← Назад к вопросам

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

1.0 Junior🔥 191 комментариев
#Python Core

Условие

Объясните разницу между shallow copy и deep copy в Python.

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)

a[0][0] = 100

print(b)  # ?
print(c)  # ?

Задача

  1. Предскажите вывод
  2. Объясните, когда использовать shallow copy, а когда deep copy
  3. Как работает оператор присваивания a = b?

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Глубокое и Поверхностное Копирование в Python

Этот вопрос тестирует понимание работы памяти, ссылок и мутируемых объектов в Python.

Предсказание Вывода

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)   # Shallow copy
c = copy.deepcopy(a)  # Deep copy

a[0][0] = 100

print(b)  # [[100, 2], [3, 4]] — ИЗМЕНИЛСЯ!
print(c)  # [[1, 2], [3, 4]] — не изменился

Почему b Изменился?

Shallow copy копирует только верхний уровень структуры. Внутренние объекты остаются теми же самыми:

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)

print(id(a))        # Разные объекты списков
print(id(b))
print(id(a[0]))     # ❌ ОДИНАКОВЫЕ объекты подсписков
print(id(b[0]))

print(a is b)       # False — разные списки
print(a[0] is b[0]) # True — один и тот же подсписок!

Диаграмма памяти:

До изменения:
a: [список1] → [[1, 2], [3, 4]]
b: [список2] → [[1, 2], [3, 4]]  (копия контейнера, но NOT содержимого)

Оба указывают на одни и те же подсписки [1, 2] и [3, 4]

После a[0][0] = 100:
a[0]: [100, 2]  ← изменяется объект на адресе 0x1000
b[0]: [100, 2]  ← b всё ещё указывает на тот же адрес 0x1000!

Deep Copy

Deep copy создаёт полную копию со всеми вложенными объектами:

import copy

a = [[1, 2], [3, 4]]
c = copy.deepcopy(a)

print(id(a))        # Разные
print(id(c))
print(id(a[0]))     # Разные подсписки
print(id(c[0]))

print(a is c)       # False
print(a[0] is c[0]) # False — разные подсписки

a[0][0] = 100
print(a)  # [[100, 2], [3, 4]]
print(c)  # [[1, 2], [3, 4]] — не изменился

Визуализация

import copy

original = {'user': {'name': 'Alice', 'age': 30}}

# Присваивание (alias)
alias = original
print(alias is original)  # True — один объект

# Shallow copy
shallow = copy.copy(original)
print(shallow is original)  # False — разные словари
print(shallow['user'] is original['user'])  # True — один подсловарь!

# Deep copy
deep = copy.deepcopy(original)
print(deep is original)  # False
print(deep['user'] is original['user'])  # False — разные подсловари

# Изменение
original['user']['name'] = 'Bob'
print(alias['user']['name'])    # Bob (alias указывает на original)
print(shallow['user']['name'])  # Bob (shallow указывает на тот же подсловарь)
print(deep['user']['name'])     # Alice (deep — независимая копия)

Оператор Присваивания a = b

Присваивание — это не копирование, а создание ссылки (alias):

a = [1, 2, 3]
b = a  # b указывает на тот же объект

print(a is b)  # True — один объект в памяти
print(id(a) == id(b))  # True

a.append(4)
print(b)  # [1, 2, 3, 4] — изменение видно через обе переменные

Три уровня копирования:

import copy

original = [[1, 2], [3, 4]]

# 1. Присваивание (alias) — копирование ссылки
alias = original
# Результат: alias и original указывают на один объект

# 2. Shallow copy — копирование контейнера
shallow = copy.copy(original)
# Результат: новый список, но с ссылками на те же подсписки

# 3. Deep copy — полное копирование
deep = copy.deepcopy(original)
# Результат: новый список со своими подсписками

print(original is alias)      # True
print(original is shallow)    # False
print(original is deep)       # False
print(original[0] is shallow[0])  # True
print(original[0] is deep[0])     # False

Когда Использовать?

Присваивание a = b:

  • Хочешь просто ещё одно имя для того же объекта
  • Передача объекта в функцию
  • Экономия памяти
def process(data):
    data[0] = 100  # Изменяет оригинальный список

my_list = [1, 2, 3]
process(my_list)  # my_list теперь [100, 2, 3]

Shallow copy copy.copy():

  • Нужна копия контейнера, но содержимое может измениться
  • Работа с простыми типами (числа, строки)
  • Оптимизация памяти
import copy
list1 = [1, 2, 3]
list2 = copy.copy(list1)
list2.append(4)
print(list1)  # [1, 2, 3] — не изменился (список был изменён)

Deep copy copy.deepcopy():

  • Нужна полностью независимая копия со всеми вложенными объектами
  • Работа со сложными структурами (списки списков, словари с вложенными словарями)
  • Когда вложенные объекты могут измениться
import copy
original = {'users': [{'name': 'Alice'}, {'name': 'Bob'}]}
copy_data = copy.deepcopy(original)
copy_data['users'][0]['name'] = 'Charlie'
print(original['users'][0]['name'])  # Alice — не изменился

Практические Примеры

import copy

# Пример 1: Shallow copy для простого списка
nums = [1, 2, 3]
copied = copy.copy(nums)
copied[0] = 99
print(nums)  # [1, 2, 3] — не изменился

# Пример 2: Deep copy для вложенных структур
config = {'db': {'host': 'localhost', 'port': 5432}}
config_copy = copy.deepcopy(config)
config_copy['db']['host'] = 'remote'
print(config['db']['host'])  # localhost — не изменился

# Пример 3: Пользовательские объекты
class Person:
    def __init__(self, name, friends):
        self.name = name
        self.friends = friends  # список

alice = Person('Alice', ['Bob', 'Charlie'])
alice_copy = copy.copy(alice)
alice_copy.friends.append('David')
print(alice.friends)  # ['Bob', 'Charlie', 'David'] — shallow!

alice_deep = copy.deepcopy(alice)
alice_deep.friends.append('Eve')
print(alice.friends)  # Не изменился — deep copy

Рекомендация

Для собеседования:

  1. Вывод: b изменился, c нет
  2. Объяснение: shallow copy копирует только контейнер, а не содержимое
  3. Присваивание: создаёт ссылку, не копирует
  4. Практика: используй deep copy для сложных структур, shallow для простых

Это демонстрирует глубокое понимание того, как Python управляет памятью и ссылками.

Глубокое и поверхностное копирование | PrepBro