如何判断 class 在 Python 3 中是否是抽象的?

How to tell if a class is abstract in Python 3?

我写了一个 metaclass,它会在 运行 时自动在字典中注册它的 classes。为了使其正常工作,它必须能够忽略抽象 classes.

该代码在 Python 2 中运行得非常好,但我 运行 撞墙试图使其与 Python 3 兼容。

当前代码如下所示:

def AutoRegister(registry, base_type=ABCMeta):
    class _metaclass(base_type):
        def __init__(self, what, bases=None, attrs=None):
            super(_metaclass, self).__init__(what, bases, attrs)

            # Do not register abstract classes.
            # Note that we do not use `inspect.isabstract` here, as
            #   that only detects classes with unimplemented abstract
            #   methods - which is a valid approach, but not what we
            #   want here.
            # :see: 
            metaclass = attrs.get('__metaclass__')
            if not (metaclass and issubclass(metaclass, ABCMeta)):
                registry.register(self)

    return _metaclass

Python2 中的用法如下所示:

# Abstract classes; these are not registered.
class BaseWidget(object):  __metaclass__ = AutoRegister(widget_registry)
class BaseGizmo(BaseWidget): __metaclass__ = ABCMeta

# Concrete classes; these get registered.
class AlphaWidget(BaseWidget): pass
class BravoGizmo(BaseGizmo): pass

不过,我想不通的是如何在 Python 3.

中完成这项工作

metaclass 如何确定它是否正在初始化 Python 3 中的摘要 class?

当我发布这个问题时,我无法摆脱我正在处理 XY Problem. As it turns out, .

的感觉

这里真正的问题是 AutoRegister 元 class 的实现依赖于对抽象 class 是什么的错误理解。 Python 与否,摘要 class 最重要的标准之一是 it is not instanciable.

在问题中发布的示例中,BaseWidgetBaseGizmo 是可实例化的,因此它们不是抽象的。

我们这里不就是分叉兔子吗?

嗯,为什么我在 AutoRegister 中工作时遇到这么多麻烦 Python 3?因为我试图构建一些行为与 classes 在 Python.

中的工作方式相矛盾的东西

inspect.isabstract 没有返回我想要的结果这一事实应该是一个主要的危险信号:AutoRegister 是保修无效者。

那么真正的解决方案是什么?

首先,我们必须认识到 BaseWidgetBaseGizmo 没有理由存在。 它们没有提供足够的功能来实例化,也没有他们是否声明了描述他们所缺少的功能的抽象方法。

有人可能会争辩说他们可以习惯于 "categorize" 他们的子 classes,但是 a) 这显然不是这种情况下发生的事情,b) quack .

相反,我们可以接受 Python 对 "abstract" 的定义:

  1. 修改BaseWidgetBaseGizmo,使它们定义一个或多个抽象方法。

    • 如果我们不能想出任何抽象方法,那么我们可以完全删除它们吗?
    • 如果我们既不能删除它们又不能使它们适当地抽象化,可能值得退后一步,看看是否有其他方法可以解决这个问题。
  2. 修改AutoRegister的定义,使其使用inspect.isabstract来判断class是否是抽象的:see final implementation.

这很酷,但是如果我无法更改基 classes 怎么办?

或者,如果您必须保持与现有代码的向后兼容性(就像我的情况一样),装饰器可能更容易:

@widget_registry.register
class AlphaWidget(object):
    pass

@widget_registry.register
class BravoGizmo(object):
    pass

PEP3119 描述了 ABCMeta metaclass "marks" 抽象方法如何创建一个 __abstractmethods__ frozenset,其中包含 class 的所有方法抽象的。因此,要检查 class cls 是否抽象,请检查 cls.__abstractmethods__ 是否为空。

我还发现 this relevant post on abstract classes 有用。