copy vs deepcopy в Python: поверхностное и глубокое копирование простыми словами
Одна из самых частых ошибок новичков в Python — непреднамеренное изменение исходных данных при работе с «копиями». Ключ к решению — понимать разницу между поверхностным копированием (shallow copy) и глубоким копированием (deep copy), а также правильно выбирать инструмент: срезы, методы коллекций, конструкторы или модуль copy.
Быстрое напоминание: присваивание — это не копирование
a = [1, [2, 3]]
b = a # просто новая ссылка на тот же объект
b[1].append(4)
print(a) # [1, [2, 3, 4]] — изменился и a, и b
Оператор = лишь создаёт ещё одну ссылку на тот же объект. Чтобы получить независимые данные, нужен именно копирующий приём.
Поверхностное копирование (shallow copy): когда достаточно
Shallow copy копирует контейнер верхнего уровня, но вложенные объекты продолжают ссылаться на те же экземпляры. Это быстро и достаточно, если структура не вложенная или вы уверены, что вложенные элементы неизменяемы (числа, строки, кортежи без изменяемых полей и т.п.).
Способы сделать поверхностную копию
- Списки:
lst[:],list(lst),lst.copy() - Словари:
dict(d),d.copy(),{**d} - Множества:
set(s),s.copy() - Универсально:
copy.copy(obj)
import copy
lst = [1, 2, 3]
lst_copy = lst[:] # или list(lst), или lst.copy()
d = {"a": 1, "b": 2}
d_copy = d.copy() # или dict(d), или {**d}
s = {1, 2, 3}
s_copy = s.copy() # или set(s)
# Универсально
x_copy = copy.copy(lst)
Где shallow copy ломается
import copy
a = [1, [2, 3]]
b = copy.copy(a) # поверхностная копия
b[1].append(4)
print(a) # [1, [2, 3, 4]] — вложенный список общий!
Вывод: если у вас есть вложенные изменяемые объекты (списки, словари, множества, пользовательские объекты с изменяемыми полями) — поверхностного копирования недостаточно.
Глубокое копирование (deep copy): когда нужно
Глубокая копия рекурсивно копирует всю структуру данных, создавая независимые объекты на всех уровнях вложенности.
import copy
a = [1, [2, 3]]
c = copy.deepcopy(a)
c[1].append(4)
print(a) # [1, [2, 3]] — оригинал не изменился
print(c) # [1, [2, 3, 4]]
Deepcopy — «тормознее» и «дороже» по памяти, зато гарантирует изоляцию данных. Используйте его, когда структура сложная и вы точно не хотите побочных эффектов.
Модуль copy: copy() и deepcopy() на практике
import copy
# Вложенные словари и списки
profile = {
"name": "Ann",
"tags": ["new"],
"prefs": {"theme": "dark"}
}
shallow = copy.copy(profile) # или profile.copy()
shallow["tags"].append("vip")
print(profile["tags"]) # ['new', 'vip'] — изменился оригинал
deep = copy.deepcopy(profile)
deep["prefs"]["theme"] = "light"
print(profile["prefs"]["theme"]) # 'dark' — оригинал не тронут
Копирование списка словарей (частый кейс)
rows = [
{"id": 1, "tags": ["a"]},
{"id": 2, "tags": ["b"]},
]
# Вариант 1: глубокая копия всей структуры
import copy
rows_deep = copy.deepcopy(rows)
# Вариант 2: «контролируемое» копирование (быстрее, если структура известна)
rows_safe = [{**r, "tags": r["tags"][:]} for r in rows]
rows_safe[0]["tags"].append("x")
print(rows[0]["tags"]) # ['a'] — оригинал не изменился
Равенство и идентичность: как проверить, что мы действительно скопировали
import copy
a = [1, [2]]
b = copy.copy(a)
c = copy.deepcopy(a)
print(a == b == c) # True — содержимое одинаковое
print(a is b) # False — разные объекты верхнего уровня
print(a[1] is b[1]) # True — shallow copy: вложенный список общий
print(a[1] is c[1]) # False — deep copy: вложенный список скопирован
Производительность: не копируйте «просто так»
Глубокое копирование — операция дорогая. Если можно создать объект «с нуля» — часто это быстрее и экономичнее. Для больших данных оценивайте стоимость через timeit.
from timeit import timeit
setup = """
import copy
data = [{"i": i, "lst": list(range(100))} for i in range(1000)]
"""
print("shallow:", timeit("copy.copy(data)", setup=setup, number=100))
print("deep: ", timeit("copy.deepcopy(data)", setup=setup, number=100))
Советы по скорости:
- Копируйте только то, что нужно (избирательно, как в примере с
rows_safe). - Избегайте deepcopy в горячих участках кода без необходимости.
- Не копируйте неизменяемые объекты — это бессмысленно (строки, числа, кортежи без вложенных изменяемых полей).
Подводные камни и тонкости
- Кортежи неизменяемы, но если внутри них есть изменяемые объекты, shallow copy сохранит общие ссылки на них.
deepcopyобходится с циклическими ссылками с помощью memo-таблицы, поэтому не зациклится, но может быть дорогим по времени и памяти.- Библиотеки могут иметь свои правила копирования. Например, в NumPy
a.view()— это не копия, аa.copy()— реальная копия буфера. - Для pandas используйте
df.copy(deep=True)для гарантированного отделения данных.
Мини-пример с пользовательским классом
import copy
from dataclasses import dataclass
@dataclass
class Point:
x: int
meta: dict
p1 = Point(10, {"tags": ["a"]})
p2 = copy.copy(p1) # meta общая ссылка
p3 = copy.deepcopy(p1) # всё независимое
p2.meta["tags"].append("b")
print(p1.meta["tags"]) # ['a', 'b'] — shallow копия
p3.meta["tags"].append("c")
print(p1.meta["tags"]) # ['a', 'b'] — deep копия не тронула оригинал
Чек-лист: copy vs deepcopy в Python
- У меня вложенные изменяемые объекты? Да → рассмотрите deepcopy; Нет → достаточно shallow.
- Нужна максимальная скорость? Копируйте выборочно, избегайте deepcopy без повода.
- Копирую список словарей? Используйте list comprehension +
dict.copy()и копирование вложенных коллекций по месту. - Работаю с внешними библиотеками (NumPy, pandas)? Ищите их «правильный» способ копирования данных.
Заключение
Поверхностное и глубокое копирование — базовый навык, который спасает от трудноуловимых багов в Python. Разбирайтесь в структуре ваших данных, выбирайте правильный инструмент и держите в уме компромисс между скоростью и безопасностью. Хотите системно укрепить базу и быстро расти в уровне? Рекомендую посмотреть программу и начать обучение по курсу «Программирование на Python с Нуля до Гуру» — стартуйте уверенно и без пробелов.
-
Создано 20.05.2026 17:03:09
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.