Reputation: 2411
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
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
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