Why does deepcopy fail with "KeyError: '__deepcopy__'" when copying custom object?

Why does deepcopy fail with "KeyError: '__deepcopy__'" when copying custom object?

我有一个 class 可以将字典转换为这样的对象

class Dict2obj(dict):
    __getattr__= dict.__getitem__

    def __init__(self, d):
        self.update(**dict((k, self.parse(v))
                           for k, v in d.iteritems()))

   @classmethod
   def parse(cls, v):
    if isinstance(v, dict):
        return cls(v)
    elif isinstance(v, list):
        return [cls.parse(i) for i in v]
    else:
        return v

当我尝试对对象进行深度复制时出现此错误

import copy
my_object  = Dict2obj(json_data)
copy_object = copy.deepcopy(my_object)

File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 172, in deepcopy
copier = getattr(x, "__deepcopy__", None)
KeyError: '__deepcopy__'

但是,如果我重写 Dict2obj class 中的 __getattr__ 函数,我就能够进行深层复制操作。请参阅下面的示例

class Dict2obj(dict):

    __getattr__= dict.__getitem__

    def __init__(self, d):
        self.update(**dict((k, self.parse(v))
                           for k, v in d.iteritems()))

    def __getattr__(self, key):
        if key in self:
            return self[key]
        raise AttributeError

    @classmethod
    def parse(cls, v):
        if isinstance(v, dict):
            return cls(v)
        elif isinstance(v, list):
            return [cls.parse(i) for i in v]
        else:
            return v

为什么我需要重写 __getattr__ 方法才能对此 class 返回的对象进行深层复制?

您的第一个 class 会出现此问题,因为 copy.deepcopy 试图调用 getattr(x, "__deepcopy__", None) 。第三个参数的意义在于,如果对象的属性不存在,它returns第三个参数。

这是在the documentation for getattr()-

中给出的

getattr(object, name[, default])

Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.

如果底层 __getattr__ 引发 AttributeError 并且为 getattr() 函数调用提供了 default 参数,则 AttributeError 被捕获通过 getattr() 函数和它 returns 默认参数,否则它会让 AttributeError 冒泡。例子-

>>> class C:
...     def __getattr__(self,k):
...             raise AttributeError('asd')
...
>>>
>>> c = C()
>>> getattr(c,'a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __getattr__
AttributeError: asd
>>> print(getattr(c,'a',None))
None

但在您的情况下,由于您直接将 dict.__getitem__ 分配给 __getattr__ ,如果在字典中找不到该名称,则会引发 KeyError 而不是 AttributeError 因此它不会被 getattr() 处理并且你的 copy.deepcopy() 失败。

您应该处理 getattr 中的 KeyError,然后改为加注 AttributeError。例子-

class Dict2obj(dict):

    def __init__(self, d):
        self.update(**dict((k, self.parse(v))
                           for k, v in d.iteritems()))

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)
    ...