【问题标题】:Why does deepcopy fail with "KeyError: '__deepcopy__'" when copying custom object?复制自定义对象时,为什么 deepcopy 失败并显示“KeyError:'__deepcopy__'”?
【发布时间】:2016-01-28 00:49:53
【问题描述】:

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

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 类中重写 __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__ 方法才能对此类返回的对象进行深度复制?

【问题讨论】:

    标签: python class deep-copy


    【解决方案1】:

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

    这是在the documentation for getattr() 中给出的-

    getattr(object, name[, default])

    返回对象的命名属性的值。名称必须是字符串。如果字符串是对象属性之一的名称,则结果是该属性的值。例如,getattr(x, 'foobar') 等价于 x.foobar。 如果命名属性不存在,如果提供则返回默认值,否则引发AttributeError

    如果底层 __getattr__ 引发 AttributeError 并且为 getattr() 函数调用提供了 default 参数,则 AttributeErrorgetattr() 函数捕获并返回默认参数,否则它会让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)
        ...
    

    【讨论】:

    • 如果我们不能更改对象类代码怎么办?有没有其他简单的方法可以简单地制作一个不与原始对象绑定的对象的副本?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多