Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does Python check whether a concrete subclass of an ABC implements the required methods?

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.

like image 687
dudenr33 Avatar asked Jul 29 '18 18:07

dudenr33


People also ask

What is a concrete class in Python?

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.

Is ABC a module in Python?

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.

Can Python abstract class have concrete methods?

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.

How do you implement an abstract method in Python?

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.


2 Answers

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.

like image 95
abarnert Avatar answered Oct 11 '22 21:10

abarnert


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.

like image 35
user2357112 supports Monica Avatar answered Oct 11 '22 22:10

user2357112 supports Monica