Intrastellar Explorer
Intrastellar Explorer

Reputation: 2411

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.

Upvotes: 3

Views: 3224

Answers (2)

jsbueno
jsbueno

Reputation: 110311

In fact, even if one uses ABCMeta as a metaclass, if there are no abstractmethods (or attributes or properties), a class is not abstract for all purposes in Python, since it can be instantiated normally.

That is why inspect.isabstract will return False on it.

If you really need this check, then, checking the class __bases__ attribute for ABC is the way to go.

And even then, if one just use the metaclass ABCMeta directly this check will fail:

class MyClass(metaclass=ABCMeta):
   pass

Now, if we write some code to flag that MyClass above as "Abstract" and another one derived from it as "not abstract", as you want with your example ChildClassFromNoAbsMethod, then any class derived from ABC would likewise be the "subclass of a class that was declared with ABCMeta metaclass".

Still, it is possible to check if a class has the ABCMeta in its metaclass hierarchy, and inspect all the way up its ancestors to "see" if it either is the first class using ABCMeta as a metaclass in its hierarchy, or a direct child of abc.ABC. But as you've probably noted, it is of little use.

You'd better just have a throw away marker abstract attribute in yourAbstractBaseClassNoAbsMethod that would have to be overriden, then both inspect.iabstract and the language mechanisms to prevent instantiation would simply work:


In [37]: class A(ABC): 
    ...:     marker = abstractmethod(lambda : None) 
    ...:                                                                                                                             

In [38]: inspect.isabstract(A)                                                                                                       
Out[38]: True

In [39]: class B(A): 
    ...:     marker = None 
    ...:                                                                                                                             

In [40]: B()                                                                                                                         
Out[40]: <__main__.B at 0x7f389bf19cd0>

In [41]: inspect.isabstract(B)                                                                                                       
Out[41]: False

Upvotes: 2

user2357112
user2357112

Reputation: 281013

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.

Upvotes: 3

Related Questions