Перейти к содержимому
davidka.net > 💻 🧠 Код 1001 > 📑 Шпаргалки > 🐍 Шпаргалки Python > Копирование объектов в Python

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

🔎 Почему вообще важно копирование объектов?

В Python переменные — это не «коробки» с данными, а скорее «ярлыки» или ссылки, указывающие на объекты в памяти. Когда вы выполняете простое присваивание, например:

list_a = [1, 2, 3]
list_b = list_a

Вы не создаете новый список. Вы просто создаете еще одну ссылку (list_b), которая указывает на тот же самый объект списка.

👉 Любое изменение list_a немедленно отразится в list_b (и наоборот), потому что это один и тот же объект.

Аналогия: Представьте, что у вас есть документ в Google Docs. Если вы отправите другу ссылку на него, он будет редактировать ваш оригинальный документ. Это и есть присваивание. А вот если вы выберете «Файл» → «Создать копию», появится полностью независимый документ. Именно для создания таких независимых копий в Python существует модуль copy.

⚙️ Два типа копирования

Модуль copy предоставляет две ключевые функции:

  • copy.copy() — создает поверхностную копию.
  • copy.deepcopy() — создает глубокую копию.

Основное различие между ними заключается в том, как они обрабатывают вложенные объекты (например, списки внутри списков или словари внутри словарей).


🟦 Поверхностное копирование (shallow copy)

copy.copy() создает новый объект-контейнер, но вместо копирования вложенных объектов он просто вставляет в новый контейнер ссылки на них.

import copy

original_list = [1, 2, [3, 4]]
shallow_copy = copy.copy(original_list)

# Изменим вложенный список в оригинале
original_list[2][0] = 99

print(f"Оригинал: {original_list}")    # Вывод: Оригинал: [1, 2, [99, 4]]
print(f"Поверхностная копия: {shallow_copy}") # Вывод: Поверхностная копия: [1, 2, [99, 4]]

Как видите, изменение вложенного списка затронуло и оригинал, и копию, потому что этот вложенный список у них общий.

ASCII-диаграмма:

original_list → [ 1, 2, ───► list_inner ]
shallow_copy  → [ 1, 2, ───► list_inner ]
                            list_inner   → [ 99, 4 ]

🟩 Глубокое копирование (deep copy)

copy.deepcopy() решает проблему общих ссылок. Эта функция рекурсивно обходит весь объект и создает полные, независимые копии всего, что встречает на своем пути, включая все вложенные объекты.

import copy

original_list = [1, 2, [3, 4]]
deep_copy = copy.deepcopy(original_list)

# Изменим вложенный список в оригинале
original_list[2][0] = 99

print(f"Оригинал: {original_list}") # Вывод: Оригинал: [1, 2, [99, 4]]
print(f"Глубокая копия: {deep_copy}")     # Вывод: Глубокая копия: [1, 2, [3, 4]]

Теперь deep_copy полностью независима.

ASCII-диаграмма:

original_list → [ 1, 2, ───► list_a ]
deep_copy     → [ 1, 2, ───► list_b ]
                            list_a   → [ 99, 4 ]
                            list_b   → [ 3, 4 ]

📑 Шпаргалка: copy.copy() vs copy.deepcopy()

Тип объектаПримерcopy.copy() (поверхностная)copy.deepcopy() (глубокая)
list (список)[1, 2, [3, 4]]Новый список, но вложенный [3, 4] общийПолностью независимая копия, вложенный список тоже копируется
dict (словарь){"a": 1, "b": {"c": 2}}Новый словарь, но вложенный {"c": 2} общийНезависимый словарь и все вложенные словари
set (множество){1, 2, (3, 4)}Новое множество, вложенные изменяемые элементы общиеНовое множество, все вложенные элементы рекурсивно копируются
tuple (кортеж)(1, 2, [3, 4])Возвращает тот же кортеж, но вложенный список общийНовый кортеж с копией вложенного списка
str, int, float, bool"hi", 42, 3.14, TrueВозвращает тот же объект (они неизменяемы, копировать нет смысла)То же самое
custom classNode(1, Node(2))Новый объект верхнего уровня, вложенные объекты общиеПолностью независимая копия объекта и всех вложенных объектов
object with __slots__Класс с __slots__Копируется только объект верхнего уровняКопируются все вложенные объекты, если они поддерживают копирование
файл / сокет / потокopen("file.txt")Ошибка (некопируемый объект)Ошибка (некопируемый объект)

🧪 Живые примеры к таблице

📌 list

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # [1, 2, [99, 4]]
print(f"deepcopy: {c}")  # [1, 2, [3, 4]]

📌 dict

import copy
a = {"a": 1, "b": {"c": 2}}
b = copy.copy(a)
c = copy.deepcopy(a)
a["b"]["c"] = 42
print(f"copy: {b}")      # {'a': 1, 'b': {'c': 42}}
print(f"deepcopy: {c}")  # {'a': 1, 'b': {'c': 2}}

📌 set

import copy
a = {1, 2, (3, 4)} # Множества могут содержать только неизменяемые типы
b = copy.copy(a)
c = copy.deepcopy(a)
print(b == c) # True (но это два независимых объекта в памяти)

📌 tuple

import copy
a = (1, 2, [3, 4]) # Кортеж неизменяем, но его элемент-список — да
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # (1, 2, [99, 4])
print(f"deepcopy: {c}")  # (1, 2, [3, 4])

📌 Custom class

import copy
class Node:
    def __init__(self, value, child=None):
        self.value = value
        self.child = child

a = Node(1, Node(2))
b = copy.copy(a)
c = copy.deepcopy(a)
a.child.value = 99
print(f"copy: {b.child.value}")      # 99
print(f"deepcopy: {c.child.value}")  # 2```

---

#### **📌 Кастомизация поведения: `__copy__` и `__deepcopy__`**

Вы можете управлять тем, как копируются ваши собственные объекты, определив в их классе специальные методы.

python
import copy
class Custom:
def init(self, x):
self.x = x
def copy(self):
print(«Вызван copy!»)
# Возвращаем новый экземпляр, но с той же ссылкой на внутренние данные
return Custom(self.x)
def deepcopy(self, memo):
# memo — словарь для отслеживания уже скопированных объектов (защита от рекурсии)
print(«Вызван deepcopy!»)
# Создаем полностью новую копию, включая вложенные данные
return Custom(copy.deepcopy(self.x, memo))

obj = Custom([1, 2])
c1 = copy.copy(obj) # Вывод: Вызван copy!
c2 = copy.deepcopy(obj) # Вывод: Вызван deepcopy!
«`


⚡ Производительность и подводные камни

  • Скорость: copy.copy() работает значительно быстрее, так как не обходит рекурсивно всю структуру данных. copy.deepcopy() может быть очень медленным на больших и глубоко вложенных объектах.
  • Некопируемые объекты: Системные ресурсы, такие как открытые файлы, сокеты или потоки, скопировать нельзя. Попытка сделать это вызовет ошибку.
  • Циклические ссылки: deepcopy достаточно умен, чтобы обрабатывать циклические ссылки (когда объект A ссылается на B, а B — обратно на A), не уходя в бесконечную рекурсию.

Когда что использовать: главные правила

  1. Используйте поверхностное копирование (copy.copy()), если ваш объект содержит только неизменяемые данные или если вы сознательно хотите, чтобы копия и оригинал разделяли общие вложенные объекты. Это быстро и эффективно.
  2. Используйте глубокое копирование (copy.deepcopy()), когда вам нужна полная, стопроцентная независимость копии от оригинала, особенно при работе со сложными структурами данных (списками, словарями, экземплярами классов).
  3. Для неизменяемых объектов (числа, строки, кортежи без изменяемых элементов) копирование, как правило, не имеет смысла — Python и так оптимизирует их использование.
  4. Помните о возможности кастомизации через __copy__ и __deepcopy__ для полного контроля над процессом.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *