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.
A concrete class which is a sub class of such abstract base class then implements the abstract base by overriding its abstract methods. The abc module defines ABCMeta class which is a metaclass for defining abstract base class. Following example defines Shape class as an abstract base class using ABCMeta.
Python has a module called abc (abstract base class) that offers the necessary tools for crafting an abstract base class. First and foremost, you should understand the ABCMeta metaclass provided by the abstract base class. The rule is every abstract class must use ABCMeta metaclass.
Abstract class can have both concrete methods as well as abstract methods. Abstract class works as a template for other classes. Using an abstract class you can define a generalized structure without providing complete implementation of every method.
In Python, we can declare an abstract method by using @abstractmethod decorator. This abstract method is present in the abc module in python, and hence, while declaring the abstract method, we have to import the abc module compulsory. The above program does not have any implementation, and we won't get any output.
user2357112's answer covers the main question here, but there's a secondary question:
why are the TypeErrors only raised when someone tries to instantiate the subclass?
If a TypeError
were raised earlier, at class creation time, it would be impossible to create hierarchies of ABCs:
class MyABC(abc.ABC):
@abstractmethod
def foo(self): pass
class MySecondABC(MyABC):
@abstractmethod
def bar(self): pass
You don't want that to raise a TypeError
because MySecondABC
doesn't define foo
, unless someone tries to instantiate MySecondABC
.
What if it were legal only for classes that added new abstract methods? Then it would be possible to create ABC hierarchies, but it would be impossible to create intermediate helper classes:
class MyABCHelper(MySecondABC):
def foo(self):
return bar(self)*2
(For a more realistic example, see the classes in collections.abc
that allow you to implement the full MutableSequence
interface by defining only 7 of the 18 methods.)
You wouldn't want a rule that made such definitions illegal.
It happens at class creation time. In Python 3.7, it's in C, in compute_abstract_methods
in Modules/_abc.c
, which is called as part of ABCMeta.__new__
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With