Ошибка Deep Copy при копировании пользовательского объекта

У меня есть класс, который преобразует словарь в такой объект

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__'

Но из-за переопределения функции getattr в классе Dict2obj я смог выполнить операцию глубокого копирования. См. пример ниже

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, чтобы выполнить глубокое копирование объектов, возвращаемых этим классом


person Anurag Sharma    schedule 28.10.2015    source источник


Ответы (1)


Проблема возникает для вашего первого класса, потому что copy.deepcopy пытается вызвать getattr(x, "__deepcopy__", None) . Значение третьего аргумента заключается в том, что если атрибут не существует для объекта, он возвращает третий аргумент.

Это указано в документации для getattr()

getattr(object, name[, default])

Возвращает значение именованного атрибута объекта. имя должно быть строкой. Если строка является именем одного из атрибутов объекта, результатом будет значение этого атрибута. Например, getattr(x, 'foobar') эквивалентен x.foobar. Если именованный атрибут не существует, возвращается значение по умолчанию, если оно предоставлено, в противном случае возникает AttributeError.

Это работает так: если базовый __getattr__ вызывает AttributeError, а аргумент default был предоставлен для вызова функции getattr(), AttributeError перехватывается функцией getattr() и возвращает аргумент по умолчанию, в противном случае он позволяет 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() терпит неудачу.

Вы должны обработать KeyError в своем getattr, а затем поднять вместо него 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)
    ...
person Anand S Kumar    schedule 28.10.2015
comment
что, если мы не можем изменить код класса объекта? Есть ли альтернативный простой способ просто сделать копию объекта, не привязанного к оригиналу? - person khaverim; 11.02.2021