Автор оригинала: Doug Hellmann.
Цель:
Предоставляет функции для дублирования объектов с использованием семантики мелкого или глубокого копирования.
Модуль copy
включает две функции, copy ()
и deepcopy ()
, для копирования существующих объектов.
Мелкие копии
неглубокая копия , созданная с помощью copy ()
, – это новый контейнер, заполненный ссылками на содержимое исходного объекта. При создании неглубокой копии объекта list
создается новый list
, и к нему добавляются элементы исходного объекта.
copy_shallow.py
import copy import functools @functools.total_ordering class MyClass: def __init__(self, name): self.name name def __eq__(self, other): return self.name other.name def __gt__(self, other): return self.name > other.name a MyClass('a') my_list [a] dup copy.copy(my_list) print(' my_list:', my_list) print(' dup:', dup) print(' dup is my_list:', (dup is my_list)) print(' dup, (dup my_list)) print('dup[0] is my_list[0]:', (dup[0] is my_list[0])) print('dup[0], (dup[0] my_list[0]))
Для неглубокой копии экземпляр MyClass
не дублируется, поэтому ссылка в списке dup
указывает на тот же объект, что и в my_list
.
$ python3 copy_shallow.py my_list: [<__main__.MyClass object at 0x101f9c160>] dup: [<__main__.MyClass object at 0x101f9c160>] dup is my_list: False dup dup[0] is my_list[0]: True dup[0]
Глубокие копии
Глубокая копия , созданная deepcopy ()
, представляет собой новый контейнер, заполненный копиями содержимого исходного объекта. Чтобы сделать полную копию list
, создается новый list
, элементы исходного списка копируются, а затем эти копии добавляются в новый список.
Замена вызова copy ()
на deepcopy ()
делает разницу в выводе очевидной.
copy_deep.py
import copy import functools @functools.total_ordering class MyClass: def __init__(self, name): self.name name def __eq__(self, other): return self.name other.name def __gt__(self, other): return self.name > other.name a MyClass('a') my_list [a] dup copy.deepcopy(my_list) print(' my_list:', my_list) print(' dup:', dup) print(' dup is my_list:', (dup is my_list)) print(' dup, (dup my_list)) print('dup[0] is my_list[0]:', (dup[0] is my_list[0])) print('dup[0], (dup[0] my_list[0]))
Первый элемент списка больше не является той же ссылкой на объект, но когда два объекта сравниваются, они все равно оцениваются как равные.
$ python3 copy_deep.py my_list: [<__main__.MyClass object at 0x101e9c160>] dup: [<__main__.MyClass object at 0x1044e1f98>] dup is my_list: False dup dup[0] is my_list[0]: False dup[0]
Настройка поведения копирования
Можно контролировать создание копий с помощью специальных методов __copy __ ()
и __deepcopy __ ()
.
__copy __ ()
вызывается без аргументов и должен возвращать неглубокую копию объекта.__deepcopy __ ()
вызывается со словарем заметок и должен возвращать глубокую копию объекта. Любые атрибуты членов, которые необходимо глубоко скопировать, следует передать вcopy.deepcopy ()
вместе со словарем заметок для контроля рекурсии. (Подробнее о мемо-словаре рассказывается позже.)
В следующем примере показано, как вызываются методы.
copy_hooks.py
import copy import functools @functools.total_ordering class MyClass: def __init__(self, name): self.name name def __eq__(self, other): return self.name other.name def __gt__(self, other): return self.name > other.name def __copy__(self): print('__copy__()') return MyClass(self.name) def __deepcopy__(self, memo): print('__deepcopy__({})'.format(memo)) return MyClass(copy.deepcopy(self.name, memo)) a MyClass('a') sc copy.copy(a) dc copy.deepcopy(a)
Словарь памяток используется для отслеживания значений, которые уже были скопированы, чтобы избежать бесконечной рекурсии.
$ python3 copy_hooks.py __copy__() __deepcopy__({})
Рекурсия в глубокой копии
Чтобы избежать проблем с дублированием рекурсивных структур данных, deepcopy ()
использует словарь для отслеживания объектов, которые уже были скопированы. Этот словарь передается методу __deepcopy __ ()
, так что он также может быть исследован там.
В следующем примере показано, как взаимосвязанная структура данных, такая как ориентированный граф, может помочь защитить от рекурсии путем реализации метода __deepcopy __ ()
.
copy_recursion.py
import copy class Graph: def __init__(self, name, connections): self.name name self.connections connections def add_connection(self, other): self.connections.append(other) def __repr__(self): return {},>{})'.format( self.name, id(self)) def __deepcopy__(self, memo): print('\nCalling __deepcopy__ for {!r}'.format(self)) if self in memo: existing memo.get(self) print(' Already copied to {!r}'.format(existing)) return existing print(' Memo dictionary:') if memo: for k, v in memo.items(): print(' {}: {}'.format(k, v)) else: print(' (empty)') dup Graph(copy.deepcopy(self.name, memo), []) print(' Copying to new object {}'.format(dup)) memo[self] dup for c in self.connections: dup.add_connection(copy.deepcopy(c, memo)) return dup root Graph('root', []) a Graph('a', [root]) b Graph('b', [a, root]) root.add_connection(a) root.add_connection(b) dup copy.deepcopy(root)
Класс Graph
включает несколько основных методов ориентированного графа. Экземпляр может быть инициализирован именем и списком существующих узлов, к которым он подключен. Метод add_connection ()
используется для установки двунаправленных соединений. Он также используется оператором глубокого копирования.
Метод __deepcopy __ ()
печатает сообщения, чтобы показать, как он вызывается, и при необходимости управляет содержимым словаря заметок. Вместо того, чтобы копировать весь список подключений оптом, он создает новый список и добавляет к нему копии отдельных подключений. Это гарантирует, что словарь памяток обновляется при дублировании каждого нового узла, и позволяет избежать проблем с рекурсией или дополнительных копий узлов. Как и раньше, по завершении метод возвращает скопированный объект.
Глубокая копия графа объектов с циклами
График, показанный на рисунке, включает в себя несколько циклов, но обработка рекурсии с помощью словаря памяток предотвращает появление ошибки переполнения стека. Когда корневой узел копируется, он производит следующий вывод.
$ python3 copy_recursion.py Calling __deepcopy__ for Memo dictionary: (empty) Copying to new object Calling __deepcopy__ for Memo dictionary: Copying to new object Calling __deepcopy__ for Already copied to Calling __deepcopy__ for Memo dictionary: 4326183824: 4367217936: 4326186344: Copying to new object
Во второй раз, когда узел root встречается во время копирования узла a , __deepcopy __ ()
обнаруживает рекурсию и повторно использует существующее значение из словарь памяток вместо создания нового объекта.
Смотрите также