2009-09-30 1 views
60

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

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

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

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

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 дать мне правильное поведение? В настоящее время я использую Python 2.6.2.

Заранее спасибо!

+0

Это работает? Есть проблемы? –

+0

Я думал, что по-прежнему возникают проблемы с общими ссылками, но вполне возможно, что я перепутал в другом месте. Я дважды проверю на основании сообщения @ MortenSiebuhr, когда у меня появится шанс и обновить результаты. –

+0

Из моего ограниченного в настоящее время понимания я бы ожидал, что copy.deepcopy (ChartConfigInstance) вернет новый экземпляр, который не будет иметь общих ссылок с оригиналом (без переопределения самой глубины). Это неверно? – emschorsch

ответ

52

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

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

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

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

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

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

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

+0

Я думаю, что я слышал, что лучше переопределить '__getstate__' /' __setstate__' для реализации копирования. Или я смущен? – u0b34a0f6ae

+1

@kaizer, они подходят для настройки травления/распиловки, а также копирования, но если вы не заботитесь о травлении, проще и проще использовать '__copy__' /' __deepcopy__'. –

+2

Это, кажется, не прямой перевод копии/глубокой копии. Ни копия, ни глубокая копия не вызывают конструктор копируемого объекта. Рассмотрим этот пример. класс Test1 (объект): Защиту __init __ (Я): печать "% s% s" % (само .__ класс __.__ name__, "__init__") класс Test2 (Test1): Защиту __copy __ (сам): новый тип = (Я)() вернуть новый t1 = Test1() copy.copy (t1) t2 = Test2() copy.copy (t2) –

5

Возможно, я немного отчуждаюсь от особенностей, но здесь идет;

От copy docs;

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

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

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

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

+2

* Чтобы класс мог определить свою собственную реализацию копии, он может определять специальные методы '__copy __ (') и '__deepcopy __()'. * Http://docs.python.org/library/copy.html – SilentGhost

+0

I ' спасибо, дважды проверьте мой код. Я буду чувствовать себя глупым, если это будет простой ошибкой в ​​другом месте: -P –

+0

@MortenSiebuhr Вы правы. Я не был полностью ясен, что copy/deepcopy будет делать что-либо по умолчанию без меня, переопределяя эти функции. Я искал фактический код, хотя позже я могу настроить (например, если я не хочу копировать все атрибуты), поэтому я дал вам голосование, но я пойду с ответом @ AlexMartinelli. Благодаря! –

52

Собирая ответ Алекс Мартелли в и комментарий Роба Янга вы получите следующий код:

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

+2

спасибо за ответ, на самом деле документов на этом нет! есть ли причина, по которой вы 'от копирования import deepcopy' как в верхней части файла, так и внутри метода' __deepcopy__'? – Anentropic

+0

Я получаю ошибку с вышеперечисленной реализацией 'class Transporter не имеет атрибута '__new __'' (python 2.7). Я пытаюсь переопределить '__deepcopy__' – bytestorm

+0

@bytestorm что такое' Transporter'? –

4

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

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

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 
+0

Не нужен ли клон и его метод '__deepcopy__', так как он будет' __deepcopy__' = None? – flutefreak7

+0

Нет. Если метод '__deepcopy__' не найден (или' obj .__ deepcopy__' возвращает None), то 'deepcopy' возвращается к стандартной функции глубокого копирования. Это можно увидеть здесь (https://github.com/python/cpython/blob/3.6/Lib/copy.py#L159) – Peter

+0

Но тогда у b не будет возможности для глубокой печати? c = deepcopy (a) будет отличаться от d = deepcopy (b), потому что d будет дефолтной ошибкой по умолчанию, где c будет иметь некоторые общие attrs с a. – flutefreak7

4

После Peter's excellent answer, чтобы реализовать пользовательский DeepCopy, с минимальными изменениями к реализации по умолчанию (например,просто изменяя поле, как мне было нужно):

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 
0

Опираясь на чистый ответ Энтони Hatchkins', вот мой вариант, когда класс в вопросе вытекает из другого пользовательского класса (ул нам нужно вызвать 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