14

I always thought one should inherit from abc.ABC when one does not want the class to be instantiated. But I've just realized that if a class has an @abstractmethod then one can also not instanciate it.

Is there any other reason to inherit from ABC?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958

2 Answers2

22

Unless you use abc.ABCMeta as the metaclass for your class (either explicitly or by inheriting from abc.ABC), using abstractmethod doesn't really do anything.

>>> from abc import abstractmethod, ABC
>>> class Foo:
...   @abstractmethod
...   def bar(self):
...     pass
...
>>> f = Foo()
>>>

Likewise, using ABCMeta doesn't mean much unless you mark at least one method as abstract:

>>> class Bar(ABC):
...     pass
...
>>> b = Bar()
>>>

It's the combination of the two that allows a class to be (nominally) uninstantiable:

>>> class Baz(ABC):
...   @abstractmethod
...   def m(self):
...     pass
...
>>> b = Baz()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Baz with abstract methods m
>>>

(Even then, note that all @abstractmethod does is add the decorated method to a set which the metaclass machinery consults when trying to instantiate the class. It is trivial to defeat that machinery:

>>> Baz.__abstractmethods__
frozenset({'m'})
>>> Baz.__abstractmethods__ = set()
>>> b = Baz()
>>>

)


Note that ABC itself is a trivial class that uses ABCMeta as its metaclass, which makes any of its descendants use it as well.

# Docstring omitted; see
# https://github.com/python/cpython/blob/3.7/Lib/abc.py#L166
# for the original
class ABC(metaclass=ABCMeta):
    __slots__ = ()
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Well, `__slots__` doesn't keep the *class object small*, it's so your inherited classes can make use of `__slots__` – juanpa.arrivillaga May 06 '19 at 16:32
  • Good point, although nothing stops a class from using `__slots__` even if its parent doesn't use it. I guess I'm missing the point of an empty `__slots__` attribute. – chepner May 06 '19 at 16:38
  • 1
    if *any* parent doens't have `__slots__` then regardless of whether or not you define `__slots__` your sublcass will still have a `__dict__` attribute with a corresponding dict namespace – juanpa.arrivillaga May 06 '19 at 16:39
  • 1
    "Note that ABC itself is a trivial class that uses ABCMeta as its metaclass, which makes any of its descendants use it as well." Yes, but not always ! Be aware of metaclasses clashs, as mentionned in the official standard: `Note that the type of ABC is still ABCMeta, therefore inheriting from ABC requires the usual precautions regarding metaclass usage, as multiple inheritance may lead to metaclass conflicts.` from – Marine Galantin Apr 07 '21 at 22:52
  • My point was that `ABC` doesn't do anything *except* instantiate `ABCMeta`. – chepner Apr 08 '21 at 13:22
0

What chepner said, and also readability. Inheriting from ABC makes it clear to your readers what you're up to.

>>> from abc import ABC, abstractmethod
>>> 
>>> class Foo:
...     @abstractmethod
...     def f(self):
...         pass
... 
>>> class Bar(Foo):
...     pass
... 
>>> Bar().f()
>>> 
>>> class Baz(ABC):
...     @abstractmethod
...     def f(self):
...         pass
... 
>>> class Quux(Baz):
...     pass
... 
>>> Quux().f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Quux with abstract methods f
Kevin Panko
  • 8,356
  • 19
  • 50
  • 61
Max Gasner
  • 1,191
  • 1
  • 12
  • 18