Python 确定类是否是抽象的 (ABC) 没有抽象方法



我有一个继承自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

AbstractB都不是直接从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

最新更新