Подтвердить что ты не робот

Как переопределить операции копирования /deepcopy для объекта Python?

Я понимаю разницу между copy и deepcopy копией в модуле копирования. Раньше copy.deepcopy успешно использовал copy.copy и copy.deepcopy, но это первый раз, когда я фактически перегрузил методы __copy__ и __deepcopy__. Я уже погуглил и просмотрел встроенные модули Python, чтобы найти экземпляры функций __copy__ и __deepcopy__ (например, sets.py, decimal.py и fractions.py), но я все еще не уверен на 100% Я правильно понял.

Вот мой сценарий:

У меня есть объект конфигурации. Первоначально я собираюсь создать экземпляр одного объекта конфигурации с набором значений по умолчанию. Эта конфигурация будет передана нескольким другим объектам (чтобы все объекты запускались с одинаковой конфигурацией). Однако после начала взаимодействия с пользователем каждый объект должен независимо настраивать свои конфигурации, не затрагивая конфигурации друг друга (что говорит мне, что мне нужно будет сделать глубокие копии моей начальной конфигурации для передачи).

Вот пример объекта:

class ChartConfig(object):

    def __init__(self):

        #Drawing properties (Booleans/strings)
        self.antialiased = None
        self.plot_style = None
        self.plot_title = None
        self.autoscale = None

        #X axis properties (strings/ints)
        self.xaxis_title = None
        self.xaxis_tick_rotation = None
        self.xaxis_tick_align = None

        #Y axis properties (strings/ints)
        self.yaxis_title = None
        self.yaxis_tick_rotation = None
        self.yaxis_tick_align = None

        #A list of non-primitive objects
        self.trace_configs = []

    def __copy__(self):
        pass

    def __deepcopy__(self, memo):
        pass 

Как правильно реализовать методы copy и deepcopy для этого объекта, чтобы обеспечить правильное поведение copy.copy и copy.deepcopy?

4b9b3361

Ответ 1

Рекомендации по настройке находятся в самом конце страницы docs:

Классы могут использовать одни и те же интерфейсы для контрольное копирование, которое они используют для контроль травления. См. Описание модульного рассола для информации о эти методы. Модуль копирования не используйте регистрацию copy_reg модуль.

Чтобы класс мог определить свой собственный копирования, он может определить специальные методы __copy__() и __deepcopy__(). Первый призван реализовать мелкую копию операция; нет дополнительных аргументов прошло. Последний призван выполнить операцию глубокой копии; Это передается один аргумент, памятка Словарь. Если __deepcopy__()осуществлению необходимо сделать глубокий копию компонента, он должен вызывать функция deepcopy() с компонента в качестве первого аргумента и словарь заметок в качестве второго аргумента.

Поскольку вы, похоже, не заботитесь о настройке травления, определение __copy__ и __deepcopy__ определенно похоже на правильный путь для вас.

В частности, __copy__ (мелкая копия) довольно легко в вашем случае...:

def __copy__(self):
  newone = type(self)()
  newone.__dict__.update(self.__dict__)
  return newone

__deepcopy__ будет аналогичным (принимая также аргумент memo), но перед возвратом ему нужно было бы вызвать self.foo = deepcopy(self.foo, memo) для любого атрибута self.foo, который нуждается в глубоком копировании (по существу атрибутов, которые являются контейнерами - списками, dicts, непримитивные объекты, которые хранят другие вещи через их __dict__ s).

Ответ 2

Объединяя ответ Alex Martelli и комментарий Роба Юнга, вы получаете следующий код:

from copy import copy, deepcopy

class A(object):
    def __init__(self):
        print 'init'
        self.v = 10
        self.z = [2,3,4]

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        return result

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            setattr(result, k, deepcopy(v, memo))
        return result

a = A()
a.v = 11
b1, b2 = copy(a), deepcopy(a)
a.v = 12
a.z.append(5)
print b1.v, b1.z
print b2.v, b2.z

печатает

init
11 [2, 3, 4, 5]
11 [2, 3, 4]

здесь __deepcopy__ заполняет memo dict, чтобы избежать избыточного копирования в случае, если на объект ссылается его член.

Ответ 3

Следуя Питеру отличный ответ, чтобы реализовать пользовательскую глубину, с минимальным изменением реализации по умолчанию (например, просто изменяя поле, как мне было нужно):

class Foo(object):
    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method

        # custom treatments
        # for instance: cp.id = None

        return cp

Ответ 4

Возможно, я немного устал от особенностей, но здесь:

Из copy docs;

  • Неглубокая копия создает новый составной объект, а затем (насколько это возможно) вставляет ссылки в него в объекты, найденные в оригинале.
  • Глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинале.

Другими словами: copy() будет копировать только верхний элемент и оставить остальные в качестве указателей в исходную структуру. deepcopy() будет рекурсивно скопировать все.

То есть deepcopy() - это то, что вам нужно.

Если вам нужно сделать что-то действительно конкретное, вы можете переопределить __copy__() или __deepcopy__(), как описано в руководстве. Лично я мог бы реализовать обычную функцию (например, config.copy_config() или такую), чтобы было ясно, что это не стандартное поведение Python.

Ответ 5

Неясно, почему вам нужно переопределить эти методы, так как вы не хотите делать какие-либо настройки для методов копирования.

Во всяком случае, если вы хотите настроить глубокую копию (например, разделяя некоторые атрибуты и копируя другие), вот решение:

from copy import deepcopy


def deepcopy_with_sharing(obj, shared_attribute_names, memo=None):
    '''
    Deepcopy an object, except for a given list of attributes, which should
    be shared between the original object and its copy.

    obj is some object
    shared_attribute_names: A list of strings identifying the attributes that
        should be shared between the original and its copy.
    memo is the dictionary passed into __deepcopy__.  Ignore this argument if
        not calling from within __deepcopy__.
    '''
    assert isinstance(shared_attribute_names, (list, tuple))
    shared_attributes = {k: getattr(obj, k) for k in shared_attribute_names}

    if hasattr(obj, '__deepcopy__'):
        # Do hack to prevent infinite recursion in call to deepcopy
        deepcopy_method = obj.__deepcopy__
        obj.__deepcopy__ = None

    for attr in shared_attribute_names:
        del obj.__dict__[attr]

    clone = deepcopy(obj)

    for attr, val in shared_attributes.iteritems():
        setattr(obj, attr, val)
        setattr(clone, attr, val)

    if hasattr(obj, '__deepcopy__'):
        # Undo hack
        obj.__deepcopy__ = deepcopy_method
        del clone.__deepcopy__

    return clone



class A(object):

    def __init__(self):
        self.copy_me = []
        self.share_me = []

    def __deepcopy__(self, memo):
        return deepcopy_with_sharing(self, shared_attribute_names = ['share_me'], memo=memo)

a = A()
b = deepcopy(a)
assert a.copy_me is not b.copy_me
assert a.share_me is b.share_me

c = deepcopy(b)
assert c.copy_me is not b.copy_me
assert c.share_me is b.share_me

Ответ 6

copy модуль использует evantually в __getstate__()/ __setstate__() протокол травления, поэтому они также являются действительными целями для переопределения.

Реализация по умолчанию просто возвращает и устанавливает __dict__ класса, так что вам не нужно вызывать super() и беспокоиться о хитром приеме Eino Gourdin, описанном выше.

Ответ 7

Основываясь на чистом ответе Энтони Хэтчкинса, здесь моя версия, где рассматриваемый класс происходит от другого пользовательского класса (st нам нужно назвать super):

class Foo(FooBase):
    def __init__(self, param1, param2):
        self._base_params = [param1, param2]
        super(Foo, result).__init__(*self._base_params)

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        super(Foo, result).__init__(*self._base_params)
        return result

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            setattr(result, k, copy.deepcopy(v, memo))
        super(Foo, result).__init__(*self._base_params)
        return result