Поверхностное и глубокое копирование
Три уровня
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)
Пройдите пример 👣 Пошагово: визуализатор покажет, какие объекты общие.
| Способ | Внешний объект | Вложенные объекты |
|---|---|---|
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
Что выведет код?
import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
b[0].append(99)
b.append([5])
print(a)
Напишите функцию 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]]
def clone_board(board):
return [row[:] for row in board]
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"] = [] |
Напишите функцию 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
def deep_copy(data):
if isinstance(data, dict):
return {k: deep_copy(v) for k, v in data.items()}
if isinstance(data, list):
return [deep_copy(x) for x in data]
return data
Как copy.deepcopy справляется со списком, содержащим ссылку на самого себя (a.append(a))?
Войдите, чтобы отмечать пройденные уроки.