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

问题描述

我有一个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。必须清除此内容以允许实例化该类。

>>> # 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 Modelabc specification的一部分。考虑它的版本和实现特定–始终测试目标版本/实现是否使用它。但是,它应该可以在CPython(Py3.6和Py3.9上的睾丸)和PyPy3(Py3.6上的睾丸)上工作。


可以调整包装器功能,以自动从抽象方法缓存中删除猴子修补的方法。如果修补了所有方法,这将使该类有资格进行实例化。

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)