Рубрики
Без рубрики

copy – повторяющиеся объекты

Автор оригинала: 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 __ () печатает сообщения, чтобы показать, как он вызывается, и при необходимости управляет содержимым словаря заметок. Вместо того, чтобы копировать весь список подключений оптом, он создает новый список и добавляет к нему копии отдельных подключений. Это гарантирует, что словарь памяток обновляется при дублировании каждого нового узла, и позволяет избежать проблем с рекурсией или дополнительных копий узлов. Как и раньше, по завершении метод возвращает скопированный объект.

digraph copy_example {
“корень”; «б» -> «корень»; «б» -> «а»; “корень” -> “а”; «корень» -> «б»; }” />

Глубокая копия графа объектов с циклами

График, показанный на рисунке, включает в себя несколько циклов, но обработка рекурсии с помощью словаря памяток предотвращает появление ошибки переполнения стека. Когда корневой узел копируется, он производит следующий вывод.

$ 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 __ () обнаруживает рекурсию и повторно использует существующее значение из словарь памяток вместо создания нового объекта.

Смотрите также