Search code examples
pythonoopabcabstract-methods

Python determine if class is abstract (ABC) without abstractmethod


I have a class that inherits from ABC, and does not have any abstractmethod.

I would like to check if it's an abstract class, and am currently stumped.

Determine if a Python class is an Abstract Base Class or Concrete prescribes using inspect.isabstract. However, this only works if an abstractmethod has been used.

How can I detect if a class inherits directly from ABC, without using inspect.isabstract?


Test Cases

# I need this to be flagged as abstract
class AbstractBaseClassNoAbsMethod(ABC):
    pass


# This is currently flaggable with `inspect.isabstract`
class AbstractBaseClassWithAbsMethod(ABC):
    @abstractmethod
    def some_method(self):
        """Abstract method."""


# I need this to be flagged as not abstract
class ChildClassFromNoAbsMethod(AbstractBaseClassNoAbsMethod):
    pass

I considered using issubclass(some_class, ABC), but this is True, even for the above ChildClassFromNoAbsMethod.


Current Best Solution

My current best solution works using __bases__. This is basically just listing parent classes, see How to get the parents of a Python class?

def my_isabstract(obj) -> bool:
    """Get if ABC is in the object's __bases__ attribute."""
    try:
        return ABC in obj.__bases__
    except AttributeError:
        return False

This is a viable solution. I am not sure if there is a better/more standard way out there.


Solution

  • AbstractBaseClassNoAbsMethod isn't abstract. Inheriting from ABC doesn't make a class abstract. inspect.isabstract is producing correct results. You'll also see that no error occurs if you try to instantiate AbstractBaseClassNoAbsMethod, while attempting to instantiate an actually abstract class raises an exception.

    If you want to test whether a class inherits directly from abc.ABC, you can do what you're already doing with __bases__, but many abstract classes don't inherit from abc.ABC. For example, this is an abstract class:

    class Abstract(metaclass=abc.ABCMeta):
        @abc.abstractmethod
        def foo(self):
            pass
    

    and so is B in this example:

    class A(abc.ABC):
        @abc.abstractmethod
        def foo(self):
            pass
    
    class B(A): pass
    

    but neither Abstract nor B inherits directly from abc.ABC.