我有一个继承自ABC
的类,没有任何abstractmethod
。
我想检查它是否是一个抽象类,目前被难住了。
确定 Python 类是抽象基类还是具体使用inspect.isabstract
规定。 但是,这仅在使用了abstractmethod
时才有效。
如何在不使用inspect.isabstract
的情况下检测类是否直接从ABC
继承?
测试用例
# 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
我考虑过使用issubclass(some_class, ABC)
,但这True
,即使对于上述ChildClassFromNoAbsMethod
也是如此。
当前最佳解决方案
我目前最好的解决方案适用于使用__bases__
. 这基本上只是列出父类,请参阅如何获取 Python 类的父类?
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
这是一个可行的解决方案。 我不确定是否有更好/更标准的方法。
AbstractBaseClassNoAbsMethod
不是抽象的。从ABC
继承不会使类抽象。inspect.isabstract
正在产生正确的结果。您还将看到,如果您尝试实例化AbstractBaseClassNoAbsMethod
,则不会发生错误,而尝试实例化实际抽象类会引发异常。
如果你想测试一个类是否直接从abc.ABC
继承,你可以做你已经用__bases__
做的事情,但是很多抽象类不会继承abc.ABC
。例如,这是一个抽象类:
class Abstract(metaclass=abc.ABCMeta):
@abc.abstractmethod
def foo(self):
pass
在这个例子中B
也是如此:
class A(abc.ABC):
@abc.abstractmethod
def foo(self):
pass
class B(A): pass
但Abstract
和B
都不是直接从abc.ABC
继承的。
事实上,即使使用 ABCMeta 作为元类,如果没有抽象方法(或属性或属性(,类在 Python 中也不是出于所有目的的抽象,因为它可以正常实例化。
这就是为什么inspect.isabstract
会在上面返回 False。
如果您确实需要此检查,则检查类__bases__
属性ABC
是要走的路。
即便如此,如果只是直接使用元类ABCMeta
,此检查也将失败:
class MyClass(metaclass=ABCMeta):
pass
现在,如果我们编写一些代码来将上面的MyClass
标记为"抽象",并从中派生另一个代码为"非抽象",正如您在示例ChildClassFromNoAbsMethod
中所希望的那样,那么从 ABC 派生的任何类同样将是"使用 ABCMeta 元类声明的类的子类"。
尽管如此,仍然可以检查一个类是否在其元类层次结构中具有ABCMeta
,并一直检查其祖先以"查看"它是否是使用 ABCMeta 作为其层次结构中的元类的第一个类,还是abc.ABC
的直接子类。但正如您可能已经注意到的那样,它几乎没有用处。
你最好在yourAbstractBaseClassNoAbsMethod
中有一个必须被覆盖的一次性标记抽象属性,然后inspect.iabstract
和防止实例化的语言机制都可以正常工作:
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