Способы копирования объектов: от простых срезов до глубокого копирования

Раздел: Работа с объектами -> Копирование объектов

Основные подходы к копированию объектов в 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: один и тот же объект, встречающийся несколько раз, копируется однократно.

Копирование объектов в Python 3 - comments

En
Python 3 copy (python)