Если мне нужна нерекурсивная глубокая копия моего объекта, следует ли переопределять copy или deepcopy в Python?

объект моего класса имеет список в качестве атрибута. То есть,

class T(object):
    def __init__(self, x, y):
        self.arr = [x, y]

когда этот объект копируется, я хочу отдельный список arr, но мелкая копия содержимого списка (например,x и y). Поэтому я решил реализовать свой собственный метод копирования, который воссоздаст список, но не элементы в нем. Но должен ли я называть это __copy__() или __deepcopy__()? Какое из них является правильным именем для того, что я делаю, согласно семантике Python?

мое предположение __copy__(). Если я позову deepcopy(), Я ожидал бы, что клон будет полностью отделен от оригинала. Однако документация говорит:

A глубокая копия создает новый составной объект, а затем рекурсивно, вставки копии в него из предметов, найденных в оригинале.

это сбивает с толку, сказав "вставки копии в него "вместо" вставляет глубокие копии в это", особенно с акцентом.

3 ответов


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

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

не совершайте ошибку, думая, что просто присвоение другого имени делает "copy":

t1 = T('google.com', 123)
t2 = t1  # this does *not* use __copy__

это просто связывает другое имя с тем же экземпляром. Скорее,__copy__ метод подключается с помощью функции:

import copy
t2 = copy.copy(t1)

на самом деле это зависит от желаемого поведения вашего класса, который влияет на решение, что переопределить (__copy__ или __deepcopy__).

в общем copy.deepcopy работает в основном правильно, он просто копирует все (рекурсивно), поэтому вам нужно только переопределить его, если есть какой-то атрибут, который не должен скопировать (когда-нибудь!).

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

тогда есть также случай, когда минимальный набор атрибутов для копирования четко не определен. В этом случае я бы также переопределить __copy__ но, может быть, поднять TypeError там и можно включить один (или несколько), выделенный copy методы.

однако, на мой взгляд,arr считается детализацией реализации, и поэтому я бы переопределить __copy__:

class T(object):
    def __init__(self, x, y):
        self.arr = [x, y]

    def __copy__(self):
        new = self.__class__(*self.arr)
        # ... maybe other stuff
        return new

просто чтобы показать, что он работает так, как ожидалось:

from copy import copy, deepcopy

x = T([2], [3])
y = copy(x)
x.arr is y.arr        # False
x.arr[0] is y.arr[0]  # True
x.arr[1] is y.arr[1]  # True

x = T([2], [3])
y = deepcopy(x)
x.arr is y.arr        # False
x.arr[0] is y.arr[0]  # False
x.arr[1] is y.arr[1]  # False

просто короткая заметка об ожиданиях:

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

lst1 = [1,2,3,4]
lst2 = list(lst1)
lst1 is lst2        # False

некоторые типы Python имеют явное copy метод, который (если присутствует) должен делать то же самое, что и __copy__. Это позволит явно передайте параметры (однако я еще не видел этого в действии):

lst3 = lst1.copy()  # python 3.x only (probably)
lst3 is lst1        # False

если ваш класс должен использоваться другими, вам, вероятно, нужно рассмотреть эти моменты, однако, если вы хотите, чтобы ваш класс работал только с copy.copy тогда просто перепишите __copy__.


Я думаю, что перезапись __copy__ - хорошая идея, как объяснено другими ответами с большой детализацией. Альтернативным решением может быть написать явный метод копирования, чтобы быть абсолютно ясным в том, что называется:

class T(object):
    def __init__(self, x, y):
        self.arr = [x, y]

    def copy(self):
        return T(*self.arr)

когда любой будущий читатель видит t.copy() затем он сразу же знает, что реализован пользовательский метод копирования. Недостатком, конечно, является то, что он не может хорошо играть со сторонними библиотеками, которые используют copy.copy(t).