Поверхностное и глубокое копирование

⏱ 25 мин · 🧩 заданий: 5

Три уровня

import copy

original = [1, [2, 3]]
alias = original                  # 1. присваивание — тот же объект
shallow = copy.copy(original)     # 2. поверхностная копия
deep = copy.deepcopy(original)    # 3. глубокая копия

original[1].append(4)
print(alias, shallow, deep)

Пройдите пример 👣 Пошагово: визуализатор покажет, какие объекты общие.

присваивание: alias ──────▶ [1, ●] ──▶ [2, 3] (один и тот же внешний список) поверхностная: shallow ────▶ [1, ●] ──┘ (новый внешний, внутренний общий) глубокая: deep ───────▶ [1, ●] ──▶ [2, 3] (всё новое)
Способ Внешний объект Вложенные объекты
b = a тот же те же
поверхностная копия новый те же (общие)
copy.deepcopy(a) новый новые (рекурсивно)

Поверхностная копия

import copy

nums = [1, 2, 3]
print(nums[:], nums.copy(), list(nums), copy.copy(nums))     # 4 способа для списка

d = {"a": 1}
print(d.copy(), dict(d), {**d}, copy.copy(d))                # для словаря

s = {1, 2}
print(s.copy(), set(s))

Для плоских структур (числа, строки внутри) поверхностной копии достаточно: неизменяемые элементы можно спокойно делить.

Когда нужна глубокая копия

Если внутри есть изменяемые объекты и копию будут менять «вглубь»:

import copy

template = {"name": "", "tags": [], "settings": {"theme": "light"}}

user1 = copy.copy(template)
user1["tags"].append("admin")                 # ой: испортили шаблон
print(template["tags"])

template = {"name": "", "tags": [], "settings": {"theme": "light"}}
user2 = copy.deepcopy(template)
user2["tags"].append("admin")
user2["settings"]["theme"] = "dark"
print(template, user2, sep="\n")

deepcopy и циклические ссылки

deepcopy запоминает уже скопированные объекты в словаре memo («id оригинала → копия»). Поэтому:

  • циклы не приводят к бесконечной рекурсии;
  • если один объект встречается дважды, в копии он тоже будет один (структура ссылок сохраняется).
import copy

shared = [0]
a = [shared, shared]
a.append(a)                  # список содержит сам себя
b = copy.deepcopy(a)
print(b[0] is b[1], b[2] is b, b[0] is shared)

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

По умолчанию copy.copy копирует __dict__ поверхностно, а deepcopy — рекурсивно. Поведение можно настроить:

import copy

class Document:
    def __init__(self, title, pages, cache=None):
        self.title = title
        self.pages = pages
        self.cache = cache or {}

    def __deepcopy__(self, memo):
        # кеш не копируем — пусть у копии будет свой пустой
        return Document(copy.deepcopy(self.title, memo), copy.deepcopy(self.pages, memo))

doc = Document("Отчёт", ["стр. 1"], cache={"rendered": "<html>"})
clone = copy.deepcopy(doc)
clone.pages.append("стр. 2")
print(doc.pages, clone.pages, clone.cache)

Неизменяемые объекты не копируются

import copy
t = (1, 2, 3)
s = "строка"
print(copy.copy(t) is t, copy.deepcopy(s) is s)   # незачем копировать то, что нельзя изменить
t2 = (1, [2])
print(copy.deepcopy(t2) is t2)                     # а кортеж с изменяемым внутри — копируется

Цена глубокого копирования

deepcopy медленный: обходит всю структуру, проверяет memo, вызывает специальные методы. Не копируйте «на всякий случай» в горячем коде. Часто лучше:

  • проектировать функции так, чтобы они не мутировали входные данные;
  • использовать неизменяемые структуры (tuple, frozenset, dataclass(frozen=True));
  • копировать только то, что действительно будете менять.

Итог

  • b = a — не копия.
  • Поверхностная копия (a[:], a.copy(), list(a), dict(a), copy.copy) — новый контейнер с теми же элементами.
  • copy.deepcopy — рекурсивная копия, справляется с циклами через memo.
  • Для своих классов — __copy__ и __deepcopy__.
  • Неизменяемые объекты не копируются: возвращается тот же объект.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Один вариант ответа

Что выведет код?

import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
b[0].append(99)
b.append([5])
print(a)
Задание 2 Программа на Python

Напишите функцию clone_board(board), которая возвращает независимую копию двумерного списка (изменение копии не должно затрагивать оригинал). Не используйте модуль copy.

Какие тесты проверяют код (2)
b = [[1, 2], [3, 4]]; c = clone_board(b); c[0][0] = 9; assert b == [[1, 2], [3, 4]], "Оригинал изменился"
assert clone_board([[1], [2]]) == [[1], [2]]
Задание 3 Заполнить таблицу

a = {"tags": ["x"]}. Изменится ли a после операции над b?

ИзменитсяНе изменится
b = a; b["new"] = 1
b = a.copy(); b["new"] = 1
b = a.copy(); b["tags"].append("y")
b = copy.deepcopy(a); b["tags"].append("y")
b = dict(a); b["tags"] = []
Отметьте ячейку — «+», пустая — «−».
Задание 4 Программа на Python

Напишите функцию deep_copy(data), которая вручную (рекурсивно) делает глубокую копию структуры из словарей, списков и неизменяемых значений. Модуль copy не использовать.

Какие тесты проверяют код (3)
src = {"a": [1, {"b": [2]}]}; dst = deep_copy(src); dst["a"][1]["b"].append(3); assert src == {"a": [1, {"b": [2]}]}
assert deep_copy([1, [2, [3]]]) == [1, [2, [3]]]
x = [1]; assert deep_copy(x) is not x
Задание 5 Один вариант ответа

Как copy.deepcopy справляется со списком, содержащим ссылку на самого себя (a.append(a))?

Войдите, чтобы отмечать пройденные уроки.