Копирование: ссылка, shallow copy и deep copy
Классический вопрос: чем отличаются присваивание, поверхностная и глубокая копия и когда вложенные данные «протекают».
Shallow copy копирует только верхний уровень контейнера, а вложенные объекты остаются общими. Deep copy рекурсивно копирует всё дерево объектов.
Три уровня «копирования»
Чёткий ответ. b = a — это не копия, а второе имя того же объекта. copy.copy(a) делает новый внешний контейнер, но вложенные объекты те же. copy.deepcopy(a) делает полностью независимую копию.
import copy
orig = [[1, 2], [3, 4]]
shallow = copy.copy(orig) # копия верхнего уровня
deep = copy.deepcopy(orig) # полностью независимая копия
orig[0].append(99) # меняем вложенный список
print("orig: ", orig)
print("shallow:", shallow) # увидел изменение — вложенный список общий
print("deep: ", deep) # независим
Вывод:
orig: [[1, 2, 99], [3, 4]] shallow: [[1, 2, 99], [3, 4]] deep: [[1, 2], [3, 4]]
Поверхностная копия «увидела» добавление 99, потому что внутренний список [1, 2] у orig и shallow — один и тот же объект. Глубокая копия скопировала и его, поэтому осталась нетронутой.
Способы сделать поверхностную копию
Для списка их несколько, и все эквивалентны copy.copy:
a = [1, 2, 3]
print(a[:]) # срез
print(list(a)) # конструктор
print(a.copy()) # метод copy
b = a.copy()
b.append(4)
print("a:", a) # оригинал не тронут (верхний уровень)
print("b:", b)
Вывод:
[1, 2, 3] [1, 2, 3] [1, 2, 3] a: [1, 2, 3] b: [1, 2, 3, 4]
Когда нужна именно глубокая копия
Если в структуре есть вложенные изменяемые объекты (списки в списках, словари со списками) и вы хотите менять копию, не задевая оригинал, — нужен deepcopy. Для плоских структур из неизменяемых элементов хватит поверхностной копии.
import copy
config = {"users": ["Аня", "Боб"], "limit": 10}
backup = copy.deepcopy(config)
config["users"].append("Ева")
print("config:", config["users"])
print("backup:", backup["users"]) # резерв не изменился
Вывод:
config: ['Аня', 'Боб', 'Ева'] backup: ['Аня', 'Боб']
Итог
b = a— не копия, а ещё одно имя того же объекта.copy.copyкопирует верхний уровень; вложенные объекты общие.copy.deepcopyрекурсивно копирует всё — нужен при вложенных изменяемых данных.