Способы копирования объектов: от простых срезов до глубокого копирования
Основные подходы к копированию объектов в Python 3
Как создать полную независимую копию объекта, включая все вложенные элементы?
Наиболее надёжный и универсальный способ - использовать функцию deepcopy() из модуля copy. Она рекурсивно копирует все уровни вложенности, создавая полностью самостоятельный объект.
import copy
original = [[1, 2, 3], [4, 5, 6]]
copied = copy.deepcopy(original)
copied[0][0] = 99
print(original) # [[1, 2, 3], [4, 5, 6]]
print(copied) # [[99, 2, 3], [4, 5, 6]]Python 3 copy (копирование объектов в python 3)
Для случаев, когда вложенные объекты не нужно копировать (достаточно ссылочной копии на один уровень), применяют copy.copy() (поверхностное копирование).
Типичная ошибка: изменение вложенного изменяемого объекта в поверхностной копии затрагивает оригинал. Решение - использовать deepcopy, если требуется полная изоляция.
Как сделать копию списка без импорта дополнительных модулей?
Можно использовать срез [:] или встроенную функцию list(). Это даёт поверхностную копию.
a = [1, 2, [3, 4]]
b = a[:]
b[2][0] = 100
print(a) # [1, 2, [100, 4]] (изменение вложенного списка отразилось)Проблема: вложенные списки остаются общими. Для словарей аналогично работает dict() или .copy().
Как скопировать объект пользовательского класса с сохранением его состояния?
Класс может определить методы __copy__ и __deepcopy__, чтобы контролировать процесс копирования.
import copy
class MyClass:
def __init__(self, data):
self.data = data
def __copy__(self):
return MyClass(self.data)
def __deepcopy__(self, memo):
return MyClass(copy.deepcopy(self.data, memo))
obj = MyClass([1, 2, 3])
copied = copy.deepcopy(obj)
copied.data[0] = 99
print(obj.data) # [1, 2, 3]
print(copied.data) # [99, 2, 3]Распространённая проблема: если класс содержит ссылки на внешние ресурсы (файлы, сокеты), глубокое копирование может привести к конфликтам. Рекомендуется реализовать __deepcopy__, исключающий такие ресурсы.
Как скопировать объект с циклическими ссылками?
Модуль copy корректно обрабатывает циклические ссылки благодаря аргументу memo. Пример:
import copy
lst = [1, 2]
lst.append(lst) # циклическая ссылка
try:
copied = copy.deepcopy(lst)
print("Копирование успешно")
except Exception as e:
print(e)Результат: Копирование успешно.
Помимо базовых примеров, рассмотрим продвинутые сценарии.
Копирование объектов с помощью pickle
import pickle
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Иван", 30)
binary = pickle.dumps(p)
p_copy = pickle.loads(binary)
print(p_copy.name) # Иван
print(p_copy.age) # 30Иван 30
Примечание: pickle копирует сериализуемые объекты, но не подходит для открытых файлов, сетевых соединений.
Глубокое копирование с исключением отдельных атрибутов
import copy
class DataHolder:
def __init__(self):
self.cache = {}
self.db_connection = None # не копируем
def __deepcopy__(self, memo):
new = DataHolder()
new.cache = copy.deepcopy(self.cache, memo)
# db_connection остаётся None
return newТакой подход позволяет избежать дублирования соединений с базой данных.
Использование copyreg для настройки сериализации
Модуль copy использует copyreg для регистрации функций копирования для типов C. Можно добавить свою функцию:
import copy
import copyreg
def _copy_example(obj):
return 42 # упрощённо
copyreg.pickle(ExampleType, _copy_example)
# теперь copy.copy(example) вернёт 42Этот приём полезен для встроенных типов, не поддерживающих копирование.
Копирование с помощью `__copy__` для изменяемых структур
class TreeNode:
def __init__(self, value, children=None):
self.value = value
self.children = children or []
def __copy__(self):
return TreeNode(self.value, self.children[:]) # поверхностное копирование дочернего списка
root = TreeNode(1, [TreeNode(2), TreeNode(3)])
import copy
root_copy = copy.copy(root)
root_copy.children[0].value = 99
print(root.children[0].value) # 99 (общий дочерний элемент)
print(root_copy.children[0].value) # 99Обратите внимание: __copy__ только поверхностное копирование, поэтому дочерние узлы остались общими.
Глубокое копирование с сохранением идентичности объектов
Аргумент memo в __deepcopy__ отслеживает уже скопированные объекты. Пример с циклическими ссылками:
import copy
a = [1]
b = [a, a]
c = copy.deepcopy(b)
print(b[0] is b[1]) # True
print(c[0] is c[1]) # True (идентичность сохранена)True True
Это важное свойство deepcopy: один и тот же объект, встречающийся несколько раз, копируется однократно.