为什么abc.ABCMeta抽象实例化检查不能用于list和dict的派生类? [英] Why doesn't the abc.ABCMeta abstract instantiation check work on derivatives of `list` and `dict`?
问题描述
我一直在尝试使用python中的abc
模块.啦
I have been experimenting a little with the abc
module in python. A la
>>> import abc
在正常情况下,如果ABC类包含未实现的abstractmethod
,则希望不会实例化该类.您知道如下:
In the normal case you expect your ABC class to not be instantiated if it contains an unimplemented abstractmethod
. You know like as follows:
>>> class MyClass(metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def mymethod(self):
... return -1
...
>>> MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods mymethod
对于任何派生类,为
OR.直到您继承某项内容为止,一切似乎都可以正常工作,如dict
或list
所示,如下所示:
OR for any derived Class. It all seems to work fine until you inherit from something ... say dict
or list
as in the following:
>>> class YourClass(list, metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def yourmethod(self):
... return -1
...
>>> YourClass()
[]
这是令人惊讶的,因为type
可能还是主要工厂或metaclass -ish之类的东西,所以我假定以下内容.
This is surprising because type
is probably the primary factory or metaclass -ish thing anyway or so I assume from the following.
>>> type(abc.ABCMeta)
<class 'type'>
>>> type(list)
<class 'type'>
从一些调查中,我发现它可以归结为简单的事情,就像将__abstractmethod__
属性添加到类的object
一样,其余的事情会自己发生:
From some investigation I found out that it boils down to something as simple as adding an __abstractmethod__
attribute to the class' object
and rest happens by itself:
>>> class AbstractClass:
... pass
...
>>> AbstractClass.__abstractmethods__ = {'abstractmethod'}
>>> AbstractClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractClass with abstract methods abstractmethod
因此,可以通过故意重写__new__
方法并清除__abstractmethods__
来简单地避免检查,如下所示:
So one can simply avoid the check by intentionally overriding the __new__
method and clearing out __abstractmethods__
as in below:
>>> class SupposedlyAbstractClass(metaclass=abc.ABCMeta):
... def __new__(cls):
... cls.__abstractmethods__ = {}
... return super(AbstractClass, cls).__new__(cls)
... @abc.abstractmethod
... def abstractmethod(self):
... return -1
...
>>> SupposedlyAbstractClass()
<__main__.SupposedlyAbstractClass object at 0x000001FA6BF05828>
此行为在Python 2.7和Python 3.7中与我亲自检查的相同.我不知道其他所有python实现是否都一样.
This behaviour is the same in Python 2.7 and in Python 3.7 as I have personally checked. I am not aware if this is the same for all other python implementations.
最后,请问问题……为什么要使其表现得如此?我们永远不应该从list
,tuple
或dict
制作抽象类吗?还是应该继续进行操作,并在实例化之前添加__new__
类方法来检查__abstractmethods__
?
Finally, down to the question ... Why has this been made to behave like so? Is it wise we should never make abstract classes out of list
, tuple
or dict
? or should I just go ahead and add a __new__
class method checking for __abstractmethods__
before instantiation?
推荐答案
问题
如果您有下一堂课,
The problem
If you have the next class:
from abc import ABC, abstractmethod
class Foo(list, ABC):
@abstractmethod
def yourmethod(self):
pass
问题在于,Foo
的对象可以被创建且没有任何错误,因为Foo.__new__(Foo)
将调用直接委派给list.__new__(Foo)
而不是ABC.__new__(Foo)
(负责检查是否所有抽象方法都在要实例化的类中实现)
the problem is that and object of Foo
can be created without any error because Foo.__new__(Foo)
delegates the call directly to list.__new__(Foo)
instead of ABC.__new__(Foo)
(which is responsible of checking that all abstract methods are implemented in the class that is going to be instantiated)
我们可以在Foo上实现__new__
并尝试调用ABC.__new__
:
We could implement __new__
on Foo and try to call ABC.__new__
:
class Foo(list, ABC):
def __new__(cls, *args, **kwargs):
return ABC.__new__(cls)
@abstractmethod
def yourmethod(self):
pass
Foo()
但是他提出了下一个错误:
But he next error is raised:
TypeError: object.__new__(Foo) is not safe, use list.__new__()
这是由于ABC.__new__(Foo)
调用object.__new__(Foo)
导致的,当Foo
继承自list
This is due to ABC.__new__(Foo)
invokes object.__new__(Foo)
which seems that is not allowed when Foo
inherits from list
您可以在Foo.__new__
上添加其他代码,以检查是否实现了要实例化的类中的所有抽象方法(基本上完成了ABC.__new__
的工作).
You can add additional code on Foo.__new__
in order to check that all abstract methods in the class to be instantiated are implemented (basically do the job of ABC.__new__
).
类似这样的东西:
class Foo(list, ABC):
def __new__(cls, *args, **kwargs):
if hasattr(cls, '__abstractmethods__') and len(cls.__abstractmethods__) > 0:
raise TypeError(f"Can't instantiate abstract class {cls.__name__} with abstract methods {', '.join(cls.__abstractmethods__)}")
return super(Foo, cls).__new__(cls)
@abstractmethod
def yourmethod(self):
return -1
现在Foo()
引发错误.但是下一个代码运行没有任何问题:
Now Foo()
raises an error. But the next code runs without any issue:
class Bar(Foo):
def yourmethod(self):
pass
Bar()
这篇关于为什么abc.ABCMeta抽象实例化检查不能用于list和dict的派生类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!