copy vs deepcopy в Python: поверхностное и глубокое копирование простыми словами

copy vs deepcopy в Python: поверхностное и глубокое копирование простыми словами

copy vs deepcopy в Python: поверхностное и глубокое копирование простыми словами

Одна из самых частых ошибок новичков в Python — непреднамеренное изменение исходных данных при работе с «копиями». Ключ к решению — понимать разницу между поверхностным копированием (shallow copy) и глубоким копированием (deep copy), а также правильно выбирать инструмент: срезы, методы коллекций, конструкторы или модуль copy.

Быстрое напоминание: присваивание — это не копирование

a = [1, [2, 3]]
b = a  # просто новая ссылка на тот же объект
b[1].append(4)
print(a)  # [1, [2, 3, 4]] — изменился и a, и b

Оператор = лишь создаёт ещё одну ссылку на тот же объект. Чтобы получить независимые данные, нужен именно копирующий приём.

Поверхностное копирование (shallow copy): когда достаточно

Shallow copy копирует контейнер верхнего уровня, но вложенные объекты продолжают ссылаться на те же экземпляры. Это быстро и достаточно, если структура не вложенная или вы уверены, что вложенные элементы неизменяемы (числа, строки, кортежи без изменяемых полей и т.п.).

Способы сделать поверхностную копию

  • Списки: lst[:], list(lst), lst.copy()
  • Словари: dict(d), d.copy(), {**d}
  • Множества: set(s), s.copy()
  • Универсально: copy.copy(obj)
  • import copy
    
    lst = [1, 2, 3]
    lst_copy = lst[:]            # или list(lst), или lst.copy()
    
    d = {"a": 1, "b": 2}
    d_copy = d.copy()            # или dict(d), или {**d}
    
    s = {1, 2, 3}
    s_copy = s.copy()            # или set(s)
    
    # Универсально
    x_copy = copy.copy(lst)
    

    Где shallow copy ломается

    import copy
    
    a = [1, [2, 3]]
    b = copy.copy(a)     # поверхностная копия
    b[1].append(4)
    print(a)  # [1, [2, 3, 4]] — вложенный список общий!
    

    Вывод: если у вас есть вложенные изменяемые объекты (списки, словари, множества, пользовательские объекты с изменяемыми полями) — поверхностного копирования недостаточно.

    Глубокое копирование (deep copy): когда нужно

    Глубокая копия рекурсивно копирует всю структуру данных, создавая независимые объекты на всех уровнях вложенности.

    import copy
    
    a = [1, [2, 3]]
    c = copy.deepcopy(a)
    c[1].append(4)
    print(a)  # [1, [2, 3]] — оригинал не изменился
    print(c)  # [1, [2, 3, 4]]
    

    Deepcopy — «тормознее» и «дороже» по памяти, зато гарантирует изоляцию данных. Используйте его, когда структура сложная и вы точно не хотите побочных эффектов.

    Модуль copy: copy() и deepcopy() на практике

    import copy
    
    # Вложенные словари и списки
    profile = {
        "name": "Ann",
        "tags": ["new"],
        "prefs": {"theme": "dark"}
    }
    
    shallow = copy.copy(profile)      # или profile.copy()
    shallow["tags"].append("vip")
    print(profile["tags"])  # ['new', 'vip'] — изменился оригинал
    
    deep = copy.deepcopy(profile)
    deep["prefs"]["theme"] = "light"
    print(profile["prefs"]["theme"])  # 'dark' — оригинал не тронут
    

    Копирование списка словарей (частый кейс)

    rows = [
        {"id": 1, "tags": ["a"]},
        {"id": 2, "tags": ["b"]},
    ]
    
    # Вариант 1: глубокая копия всей структуры
    import copy
    rows_deep = copy.deepcopy(rows)
    
    # Вариант 2: «контролируемое» копирование (быстрее, если структура известна)
    rows_safe = [{**r, "tags": r["tags"][:]} for r in rows]
    
    rows_safe[0]["tags"].append("x")
    print(rows[0]["tags"])  # ['a'] — оригинал не изменился
    

    Равенство и идентичность: как проверить, что мы действительно скопировали

    import copy
    
    a = [1, [2]]
    b = copy.copy(a)
    c = copy.deepcopy(a)
    
    print(a == b == c)  # True — содержимое одинаковое
    print(a is b)       # False — разные объекты верхнего уровня
    print(a[1] is b[1]) # True  — shallow copy: вложенный список общий
    print(a[1] is c[1]) # False — deep copy: вложенный список скопирован
    

    Производительность: не копируйте «просто так»

    Глубокое копирование — операция дорогая. Если можно создать объект «с нуля» — часто это быстрее и экономичнее. Для больших данных оценивайте стоимость через timeit.

    from timeit import timeit
    setup = """
    import copy
    data = [{"i": i, "lst": list(range(100))} for i in range(1000)]
    """
    print("shallow:", timeit("copy.copy(data)", setup=setup, number=100))
    print("deep:   ", timeit("copy.deepcopy(data)", setup=setup, number=100))
    

    Советы по скорости:

  • Копируйте только то, что нужно (избирательно, как в примере с rows_safe).
  • Избегайте deepcopy в горячих участках кода без необходимости.
  • Не копируйте неизменяемые объекты — это бессмысленно (строки, числа, кортежи без вложенных изменяемых полей).
  • Подводные камни и тонкости

  • Кортежи неизменяемы, но если внутри них есть изменяемые объекты, shallow copy сохранит общие ссылки на них.
  • deepcopy обходится с циклическими ссылками с помощью memo-таблицы, поэтому не зациклится, но может быть дорогим по времени и памяти.
  • Библиотеки могут иметь свои правила копирования. Например, в NumPy a.view() — это не копия, а a.copy() — реальная копия буфера.
  • Для pandas используйте df.copy(deep=True) для гарантированного отделения данных.
  • Мини-пример с пользовательским классом

    import copy
    from dataclasses import dataclass
    
    @dataclass
    class Point:
        x: int
        meta: dict
    
    p1 = Point(10, {"tags": ["a"]})
    p2 = copy.copy(p1)       # meta общая ссылка
    p3 = copy.deepcopy(p1)   # всё независимое
    
    p2.meta["tags"].append("b")
    print(p1.meta["tags"])  # ['a', 'b'] — shallow копия
    
    p3.meta["tags"].append("c")
    print(p1.meta["tags"])  # ['a', 'b'] — deep копия не тронула оригинал
    

    Чек-лист: copy vs deepcopy в Python

  • У меня вложенные изменяемые объекты? Да → рассмотрите deepcopy; Нет → достаточно shallow.
  • Нужна максимальная скорость? Копируйте выборочно, избегайте deepcopy без повода.
  • Копирую список словарей? Используйте list comprehension + dict.copy() и копирование вложенных коллекций по месту.
  • Работаю с внешними библиотеками (NumPy, pandas)? Ищите их «правильный» способ копирования данных.
  • Заключение

    Поверхностное и глубокое копирование — базовый навык, который спасает от трудноуловимых багов в Python. Разбирайтесь в структуре ваших данных, выбирайте правильный инструмент и держите в уме компромисс между скоростью и безопасностью. Хотите системно укрепить базу и быстро расти в уровне? Рекомендую посмотреть программу и начать обучение по курсу «Программирование на Python с Нуля до Гуру» — стартуйте уверенно и без пробелов.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий