На этом шаге мы рассмотрим особенности реализации этой операции.
Вопрос, на который мы по-прежнему должны ответить, состоит в том, как создавать копии (мелкие и глубокие) произвольных объектов, включая собственные классы. Теперь давайте обратимся к этому вопросу.
И снова на выручку приходит модуль copy. Его функции copy.copy() и copy.deepcopy() могут использоваться для создания дубликата любого объекта.
И снова наилучший способ понять, как их использовать, - поставить простой эксперимент. Возьмем за основу предыдущий пример с копированием списка. Давайте начнем с определения простого класса двумерной точки:
>>> class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return f'Point({self.x!r}, {self.y!r})'
Надеюсь, вы согласитесь, что это было довольно прямолинейно. Мы добавили реализацию __repr__(), с тем чтобы мы могли легко проинспектировать создаваемые на основе этого класса объекты в интерпретаторе Python.
Далее мы создадим экземпляр Point, а затем его (мелко) скопируем, использовав модуль copy:
>>> a = Point(23, 42) >>> b = copy.copy(a)
Если проинспектировать содержимое оригинального объекта Point и его (мелкого) клона, то мы увидим то, что и ожидали:
>>> a Point(23, 42) >>> b Point(23, 42) >>> a is b False
Следует иметь в виду кое-что еще. Поскольку наш объект-точка для своих координат использует примитивные типы (целые числа), то в данном случае нет никакой разницы между мелкой и глубокой копией. Но мы расширим пример секунду спустя.
Теперь перейдем к более сложному примеру. Определим еще один класс, который будет представлять двумерные прямоугольники. Сделаем это таким образом, который позволяет создавать более сложную иерархию объектов, - прямоугольники будут использовать объекты Point, представляющие их координаты:
>>> class Rectangle: def __init__(self, topleft, bottomright): self.topleft = topleft self.bottomright = bottomright def __repr__(self): return (f'Rectangle({self.topleft!r},' f'{self.bottomright!r})')
Сначала мы попытаемся создать мелкую копию экземпляра Rectangle:
>>> rect = Rectangle(Point(0, 1), Point(5, 6)) >>> srect = copy.copy(rect)
Если вы проинспектируете оригинальный прямоугольник и его копию, то увидите, что переопределение метода __repr__() прекрасно сработало и процесс мелкого копирования был выполнен, как мы и ждали:
>>> rect Rectangle(Point(0, 1),Point(5, 6)) >>> srect Rectangle(Point(0, 1),Point(5, 6)) >>> rect is srect False
Помните, как в предыдущем примере со списком иллюстрировалась разница между глубокими и мелкими копиями? Здесь мы собираемся применить тот же самый подход. Изменим объект, находящийся глубоко в иерархии объектов, а затем вы вновь увидите, как это изменение будет отражено в (мелкой) копии:
>>> rect.topleft.x = 999 >>> rect Rectangle(Point(999, 1),Point(5, 6)) >>> srect Rectangle(Point(999, 1),Point(5, 6))
Надеемся, что этот пример показал то, что вы ожидали. Далее, создадим глубокую копию оригинального прямоугольника. Затем внесем в нее одно изменение, и вы увидите, какие объекты были затронуты:
>>> drect = copy.deepcopy(srect) >>> drect.topleft.x = 222 >>> drect Rectangle(Point(222, 1),Point(5, 6)) >>> rect Rectangle(Point(999, 1),Point(5, 6)) >>> srect Rectangle(Point(999, 1),Point(5, 6))
Вуаля! На этот раз глубокая копия (drect) полностью независима от оригинала (rect) и мелкой копии (srect).
В этих шагах мы рассмотрели многие вопросы, и при этом остались еще некоторые тонкости, связанные с копированием объектов.
Эта тема стоит того, чтобы в нее углубиться (еще бы!), поэтому, возможно, вам стоит плотнее заняться документацией модуля copy.
См. документацию Python "Операции мелкого и глубокого копирования": https://docs.python.org/3/library/copy.html.
Например, объекты могут управлять тем, как они копируются, если в них определить специальные методы __copy__() и __deepcopy__(). Приятного времяпрепровождения!
На следующем шаге мы подитожим изученный материал.