在尝试编写单元测试以检查抽象基类的具体子类是否在实例化时是否确实实现了TypeError的情况下(如果未实现所需的方法之一),我偶然发现了一些使我想知道何时检查所需的方法的错误方法是由实际执行的具体子类定义的。
直到现在,我会说:在对象实例化之后,因为这是运行程序时实际引发Exception的时间。
但是请看以下代码片段:
import abc
class MyABC(abc.ABC):
@abstractmethod
def foo(self): pass
MyConcreteSubclass(MyABC):
pass
如预期的那样,尝试实例化MyConcreteSubclass会引发TypeError:
>>> MyConcreteSubclass()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-39-fbfc0708afa6> in <module>()
----> 1 t = MySubclass()
TypeError: Can't instantiate abstract class MySubclass with abstract methods foo
但是,如果我先声明一个有效的子类,然后再删除此方法,会发生什么呢?
class MyConcreteSubclass(MyABC):
def foo(self):
print("bar")
MyConcreteSubclass.foo
--> <function __main__.MyConcreteSubclass.foo(self)>
>>> t = MyConcreteSubclass()
>>> t.foo()
bar
>>> del MyConcreteSubclass.foo
>>> MyConcreteSubclass.foo
<function __main__.MyABC.foo(self)>
>>> t = MyConcreteSubclass()
>>> print(t.foo())
None
这当然不是我所期望的。在删除后检查MyConcreteSubclass.foo时,我们看到通过决议方法的顺序检索了基类的Abstract方法,这与最初没有在具体子类中实现foo的行为相同。>
但是在实例化之后,不会引发TypeError。 所以我想知道,当解释器评估具体子类的主体时,是否已经执行了所需方法的检查? 如果是这样,为什么只有在有人尝试实例化子类时才会引发TypeErrors?
上面显示的测试是使用Python 3.6.5执行的。
答案 0 :(得分:14)
它在类创建时发生。在Python 3.7中,它在C中,在Modules/_abc.c
中的compute_abstract_methods
中,它被称为ABCMeta.__new__
的一部分。
偶然地,docs确实提到了
不动态地向类添加抽象方法,或在创建方法或类后尝试修改其抽象状态。
答案 1 :(得分:7)
user2357112's answer在这里涵盖了主要问题,但还有一个次要问题:
为什么只有在有人尝试实例化子类时才会引发TypeErrors?
如果提前提出TypeError
,则在创建类时,将不可能创建ABC的层次结构:
class MyABC(abc.ABC):
@abstractmethod
def foo(self): pass
class MySecondABC(MyABC):
@abstractmethod
def bar(self): pass
您不希望这样引发TypeError
,因为除非有人尝试实例化MySecondABC
,否则foo
并没有定义MySecondABC
。
如果仅对添加了新抽象方法的类合法,该怎么办?这样就可以创建ABC层次结构,但不可能创建中间帮助程序类:
class MyABCHelper(MySecondABC):
def foo(self):
return bar(self)*2
(有关更现实的示例,请参见collections.abc
中的类,这些类允许您仅通过定义18种方法中的7种来实现完整的MutableSequence
接口。)
您不希望使用将此类定义视为非法的规则。