Determine if a Python class is an Abstract Base Class or Concrete
import inspectprint(inspect.isabstract(object)) # Falseprint(inspect.isabstract(MessageDisplay)) # Trueprint(inspect.isabstract(FriendlyMessageDisplay)) # Trueprint(inspect.isabstract(FriendlyMessagePrinter)) # False
This checks that the internal flag TPFLAGS_IS_ABSTRACT
is set in the class object, so it can't be fooled as easily as your implementation:
class Fake: __abstractmethods__ = 'bluh'print(is_abstract(Fake), inspect.isabstract(Fake)) # True, False
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 valueTrue
. TheABCMeta.__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 flagPy_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)) # Falseprint(is_abstract(MessageDisplay)) # Trueprint(is_abstract(FriendlyMessageDisplay)) # Trueprint(is_abstract(FriendlyMessagePrinter)) # False
You could do this with the _ast
module. For example, if your example code were in foo.py
you could invoked this function with "foo.py"
and "FriendlyMessagePrinter"
as arguments.
def is_abstract(filepath, class_name): astnode = compile(open(filename).read(), filename, 'exec', _ast.PyCF_ONLY_AST) for node in astnode.body: if isinstance(node, _ast.ClassDef) and node.name == class_name: for funcdef in node.body: if isinstance(funcdef, _ast.FunctionDef): if any(not isinstance(n, _ast.Pass) for n in funcdef.body): return False return True print 'class %s not found in file %s' %(class_name, filepath)