ABC 拒绝我的子类,尽管它没有抽象方法

ABC refuses my subclass, despite it having no abstract methods

我有一个 collections.abc.MutableMapping 子类,它通过猴子修补实现了所需的抽象方法:

from collections.abc import MutableMapping

def make_wrappers(cls, methods = []):
    """This is used to eliminate code repetition, this file contains around 12
    classes with similar rewirings of self.method to self.value.method
    This approach is used instead of overriding __getattr__ and __getattribute__
    because those are bypassed by magic methods like add()
    """
    for method in methods:
        def wrapper(self, *args, _method=method, **kwargs):
            return getattr(self.value, _method)(*args, **kwargs)
        setattr(cls, method, wrapper)

class MySubclass(MutableMapping):
    def __init__(self, value = None):
        value = {} if value is None else value
        self.value = value

make_wrappers(
    MySubclass, 
    ['__delitem__', '__getitem__', '__iter__', '__len__', '__setitem__']
)

在尝试实例化 MySubclass 时,出现此错误:

>>> c = MySubclass({'a':a, 'b':b})

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    c = MySubclass({'a':1, 'b':2})
TypeError: Can't instantiate abstract class MySubclass with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__

但这行得通:

>>> MySubclass.__setitem__

<function make_wrappers.<locals>.wrapper at 0x0000020F76A24AF0>

如何强制实例化?

知道 这些方法有效,因为当我在 MySubclasscollections.abc.MutableMapping 之间放置一个额外的继承层时,它们神奇地起作用了!

正在创建 ABC creates a set of missing abstract methods as soon as the class is created。必须清除此项以允许实例化 class.

>>> # setup as before
>>> MySubclass.__abstractmethods__
frozenset({'__delitem__', '__getitem__', '__iter__', '__len__', '__setitem__'})
>>> MySubclass({'a':a, 'b':b})
# TypeError: Can't instantiate abstract class MySubclass with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__
>>> MySubclass.__abstractmethods__ = frozenset()  # clear cache of abstract methods
>>> MySubclass({'a':a, 'b':b})
<__main__.MySubclass at 0x1120a9340>

请注意 .__abstractmethods__ 不是 Python Data Model or abc specification 的一部分。考虑它的版本和具体实现——总是测试你的目标 version/implementation 是否使用它。但是,它应该适用于 CPython(Py3.6 和 Py3.9 上的测试)和 PyPy3(Py3.6 上的测试)。


可以调整包装函数以自动从抽象方法缓存中删除 monkey-patched 方法。如果所有方法都被修补,这使得 class 有资格实例化。

def make_wrappers(cls, methods = []):
    """This is used to eliminate code repetition, this file contains around 12
    classes with similar rewirings of self.method to self.value.method
    This approach is used instead of overriding __getattr__ and __getattribute__
    because those are bypassed by magic methods like add()
    """
    for method in methods:
        def wrapper(self, *args, _method=method, **kwargs):
            return getattr(self.value, _method)(*args, **kwargs)
        setattr(cls, method, wrapper)
    cls.__abstractmethods__ = cls.__abstractmethods__.difference(methods)