4

我编写了一个元类,它在运行时自动将其类注册到字典中。为了使其正常工作,它必须能够忽略抽象类。

该代码在 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: http://stackoverflow.com/a/14410942/
            metaclass = attrs.get('__metaclass__')
            if not (metaclass and issubclass(metaclass, ABCMeta)):
                registry.register(self)

    return _metaclass

Python 2 中的用法如下所示:

# 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 中完成这项工作。

元类如何确定它是否正在初始化 Python 3 中的抽象类?

4

2 回答 2

2

PEP3119描述了 ABCMeta 元类如何“标记”抽象方法并创建一个__abstractmethods__包含仍然是抽象类的所有方法的冻结集。因此,要检查一个类cls是否是抽象的,请检查是否cls.__abstractmethods__为空。

我还发现这篇关于抽象类的相关帖子很有用。

于 2017-03-03T09:00:56.880 回答
1

当我发布这个问题时,我无法动摇我正在处理XY Problem的感觉。事实证明,这正是发生的事情

这里真正的问题是,AutoRegister元类在实现时依赖于对抽象类是什么的错误理解。不管是不是Python,抽象类最重要的标准之一是它是不可实例化的。

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

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

AutoRegister好吧,为什么我在使用 Python 3 工作时遇到这么多麻烦?因为我试图构建一些行为与 Python 中类的工作方式相矛盾的东西。

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

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

首先,我们必须承认这一点,BaseWidget并且BaseGizmo 没有理由存在。 它们没有提供足够的可实例化功能,也没有声明描述它们缺少的功能的抽象方法。

有人可能会争辩说它们可以用来“分类”它们的子类,但是 a) 这显然不是在这种情况下发生的事情,并且 b) quack

相反,我们可以接受 Python 对“抽象”的定义:

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

    • 如果我们不能提出任何抽象方法,那么我们可以完全删除它们吗?
    • 如果我们不能删除它们但也不能使它们正确抽象,那么可能值得退后一步,看看是否有其他方法可以解决这个问题。
  2. 修改 的定义,AutoRegister以便它inspect.isabstract用来决定一个类是否是抽象的:参见最终实现

这很酷,但是如果我不能更改基类怎么办?

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

@widget_registry.register
class AlphaWidget(object):
    pass

@widget_registry.register
class BravoGizmo(object):
    pass
于 2016-08-11T00:31:16.480 回答