While trying to write unittests that check whether a concrete subclass of an Abstract base class really does raise a TypeError upon instantiation if one of the required methods is not implemented, I stumbled upon something which made me wonder when the check if the required methods is defined by the concrete subclass is actually performed.
Until now I would have said: upon instantiation of the object, since this is the time when the Exception is actually raised when running the program.
But look at this snippet:
import abc class MyABC(abc.ABC): @abstractmethod def foo(self): pass MyConcreteSubclass(MyABC): pass
As expected, trying to instantiate MyConcreteSubclass raises a 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
But what happens if I declare a valid subclass at first and then afterwards delete this method surprises me:
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
This is certainly not what I expected. When inspecting MyConcreteSubclass.foo after deletion, we see that through the method Resolution order the Abstract method of the base class is retrieved, which is the same behaviour as if we haven't implemented foo in the concrete subclass in the first place.
But after instantiation the TypeError is not raised. So I wonder, are the checks whether the required methods are implemented already performed when the body of the concrete subclass is evaluated by the Interpreter? If so, why are the TypeErrors only raised when someone tries to instantiate the subclass?
The Tests shown above were performed using Python 3.6.5.
It happens at class creation time. In Python 3.7, it's in C, in
Modules/_abc.c, which is called as part of
Incidentally, the docs do mention that
Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are not supported.