Копирование объектов: поверхностное и глубокое
Почему копирование — это не так просто? 🔍
В Python переменные хранят не сами объекты, а ссылки на них. Это приводит к неожиданностям:
list_a = [1, 2, [3, 4]]
list_b = list_a # Просто копируем ссылку!
list_b[0] = 99
print(list_a) # [99, 2, [3, 4]] 😱
Поменяли list_b — изменился и list_a! Это называется поверхностным копированием (shallow copy). Как этого избежать? Держи два решения!
Методы поверхностного копирования 🍰
1. Срез [:]
Работает для списков и других последовательностей:
list_a = [1, 2, [3, 4]]
list_b = list_a[:] # Создаём новый список
list_b[0] = 99
print(list_a) # [1, 2, [3, 4]] ✅
2. copy() метод
Универсальный способ:
list_a = [1, 2, [3, 4]]
list_b = list_a.copy() # То же самое
3. copy модуль
Для любых объектов:
import copy
list_b = copy.copy(list_a)
Но есть нюанс! Вложенные объекты всё равно остаются общими:
list_b[2][0] = 777
print(list_a) # [1, 2, [777, 4]] 😬
Глубокое копирование на страже 🛡️
Решает проблему вложенных объектов с помощью copy.deepcopy():
import copy
list_a = [1, 2, [3, 4]]
list_b = copy.deepcopy(list_a) # Полная копия!
list_b[2][0] = 999
print(list_a) # [1, 2, [3, 4]] 💪
Как это работает:
- Рекурсивно обходит все вложенные объекты
- Создаёт их точные независимые копии
- Подходит даже для пользовательских классов!
Когда что использовать? 🤔
| Ситуация | Метод | Пример использования |
|---|---|---|
| Простые плоские списки | copy() или [:] |
Настройки приложения |
| Сложные вложенные структуры | deepcopy() |
Конфиги с многоуровневыми словарями |
| Объекты с циклическими ссылками | deepcopy() |
Графы, деревья |
Практикуем на живом примере 🌱
Допустим, разрабатываем игру. Нужно сохранять состояние уровня:
import copy
level = {
"name": "Dungeon",
"enemies": [{"type": "Orc", "hp": 100}, {"type": "Goblin", "hp": 30}],
"items": ["sword", "potion"]
}
# Сохраняем состояние
save_game = copy.deepcopy(level)
# Меняем текущий уровень
level["enemies"][0]["hp"] = 50
print("Original:", level["enemies"][0]["hp"]) # 50
print("Save:", save_game["enemies"][0]["hp"]) # 100 🎮
Под капотом: как работает deepcopy? ⚙️
- Создаёт новый пустой объект
- Запоминает все скопированные объекты (чтобы избежать циклов)
- Рекурсивно копирует атрибуты и элементы
- Особые случаи:
- Модули не копируются
- Файловые дескрипторы остаются общими
class Hero:
def __init__(self, name):
self.name = name
hero = Hero("Aragorn")
hero_copy = copy.deepcopy(hero) # Работает!
Важные исключения 🚨
Не всё можно (и нужно) копировать:
- Файловые объекты
- Сетевые соединения
- Потоки (threading)
- Модули и классы
Для таких случаев используют другие подходы — например, сериализацию.
Проверь себя: викторина ❓
- Что выведет код?
a = [1, [2, 3]]
b = a.copy()
b[1][0] = 99
print(a[1][0])