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

Почему копирование — это не так просто? 🔍

В 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]] 💪

Как это работает:

  1. Рекурсивно обходит все вложенные объекты
  2. Создаёт их точные независимые копии
  3. Подходит даже для пользовательских классов!

Когда что использовать? 🤔

Ситуация Метод Пример использования
Простые плоские списки 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? ⚙️

  1. Создаёт новый пустой объект
  2. Запоминает все скопированные объекты (чтобы избежать циклов)
  3. Рекурсивно копирует атрибуты и элементы
  4. Особые случаи:
  5. Модули не копируются
  6. Файловые дескрипторы остаются общими
class Hero:
    def __init__(self, name):
        self.name = name

hero = Hero("Aragorn")
hero_copy = copy.deepcopy(hero)  # Работает!

Важные исключения 🚨

Не всё можно (и нужно) копировать: - Файловые объекты - Сетевые соединения - Потоки (threading) - Модули и классы

Для таких случаев используют другие подходы — например, сериализацию.


Проверь себя: викторина ❓

  1. Что выведет код?
a = [1, [2, 3]]
b = a.copy()
b[1][0] = 99
print(a[1][0])
Скрыть рекламу навсегда

📘 VK Видео — обучение без ограничений

Все уроки доступны без VPN, без блокировок и зависаний.

Можно смотреть с телефона, планшета или компьютера — в любое время.

▶️ Смотреть на VK Видео