Abstract classes and their concrete implementations have an __abstractmethods__
attribute containing the names of abstract methods and properties that have not been implemented. This behaviour is described in PEP 3199:
Implementation: The @abstractmethod
decorator sets the function attribute __isabstractmethod__
to the value True
. The ABCMeta.__new__
method computes the type attribute __abstractmethods__
as the set of all method names that have an __isabstractmethod__
attribute whose value is true. It does this by combining the __abstractmethods__
attributes of the base classes, adding the names of all methods in the new class dict that have a true __isabstractmethod__
attribute, and removing the names of all methods in the new class dict that don't have a true __isabstractmethod__
attribute. If the resulting __abstractmethods__
set is non-empty, the class is considered abstract, and attempts to instantiate it will raise TypeError. (If this were implemented in CPython, an internal flag Py_TPFLAGS_ABSTRACT
could be used to speed up this check.)
So in concrete classes, this attribute either will not exist or will be an empty set. This is easy to check:
def is_abstract(cls):
if not hasattr(cls, "__abstractmethods__"):
return False # an ordinary class
elif len(cls.__abstractmethods__) == 0:
return False # a concrete implementation of an abstract class
else:
return True # an abstract class
Or more succinctly:
def is_abstract(cls):
return bool(getattr(cls, "__abstractmethods__", False))
print(is_abstract(object)) # False
print(is_abstract(MessageDisplay)) # True
print(is_abstract(FriendlyMessageDisplay)) # True
print(is_abstract(FriendlyMessagePrinter)) # False