Determine if a Python class is an Abstract Base Class or Concrete Determine if a Python class is an Abstract Base Class or Concrete python python

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 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))                 # 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)