Как работает механизм присваивания и передачи объектов по ссылке в Python?

Ответ

В Python переменные — это имена (names), которые ссылаются на объекты (objects) в памяти. Присваивание (=) связывает имя с объектом, а не копирует сам объект. Это называется передачей по ссылке на объект.

Ключевое правило: «Объекты не копируются при присваивании».

Пример 1: Изменяемые объекты (списки, словари, множества)

a = [1, 2, 3]  # Создается список, `a` ссылается на него
b = a          # `b` теперь ссылается на ТОТ ЖЕ объект в памяти

a.append(4)    # Изменяем объект через ссылку `a`
print(a)       # [1, 2, 3, 4]
print(b)       # [1, 2, 3, 4] - `b` «видит» изменения, так как ссылается на тот же объект
print(a is b)  # True - это один и тот же объект (проверка по id)

Пример 2: Неизменяемые объекты (числа, строки, кортежи)

x = 10  # `x` ссылается на объект `int` со значением 10
y = x   # `y` ссылается на тот же объект `10`
x = x + 5  # Операция создает НОВЫЙ объект `int(15)`. `x` теперь ссылается на него.
print(x)   # 15
print(y)   # 10 - `y` продолжает ссылаться на старый объект `10`

С неизменяемыми объектами безопасно, так как их нельзя изменить «на месте».

Создание независимых копий:

  • Поверхностное копирование (copy()): Создает новый контейнер, но вкладывает в него ссылки на те же вложенные объекты.
    import copy
    list1 = [1, [2, 3]]
    list2 = copy.copy(list1)  # Поверхностная копия
    list1[0] = 100  # Изменяется только list1
    list1[1].append(4)  # Изменяется вложенный список, что затронет list2!
    print(list1)  # [100, [2, 3, 4]]
    print(list2)  # [1, [2, 3, 4]]  # Вложенный список общий!
  • Глубокое копирование (deepcopy()): Рекурсивно создает новые копии всех объектов, включая вложенные.
    list3 = copy.deepcopy(list1)  # Глубокая копия
    list1[1].append(5)
    print(list1)  # [100, [2, 3, 4, 5]]
    print(list3)  # [100, [2, 3, 4]]  # Полностью независимая копия

Практический вывод: При передаче изменяемых объектов в функции или при присваивании помните, что вы работаете с исходным объектом. Если нужна независимая копия — используйте copy или deepcopy.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши питоновские переменные. Сидишь такой, пишешь код, думаешь — всё понятно, а потом бац, и нихуя не работает. А всё потому, что не вник в эту простую, но ёбучую идею.

Смотри, в Python переменная — это не коробка, куда ты что-то кладёшь. Это, блядь, просто бирка, наклейка, бумажка с именем, которую ты прилепляешь к какому-то объекту в памяти. Оператор = — это не «положить в коробку», а «приклеить бирку на штуку». И самое главное: когда ты пишешь b = a, ты не создаёшь копию штуки, ты просто лепишь вторую такую же бирку на ту же самую, ёпта, штуку!

Правило номер раз, железобетонное: «Объекты не копируются при присваивании, ни хуя себе».

Пример первый: с изменяемыми объектами (списки, словари) Вот смотри, как обмануться можно.

a = [1, 2, 3]  # Создал список, прилепил на него бирку `a`
b = a          # Взял вторую бирку `b` и прилепил её на ТОТ ЖЕ САМЫЙ список. Овердохуища!

a.append(4)    # Через бирку `a` добавил в список четвёрку
print(a)       # [1, 2, 3, 4] — логично
print(b)       # [1, 2, 3, 4] — а вот и сюрприз! Бирка `b` ведёт к тому же месту, она тоже «видит» изменения.
print(a is b)  # True — это проверка «а не одна ли это и та же хрень?». Да, одна.

Вот и вся магия. Два имени, один объект. Изменяешь через одно — меняется и для другого. Подозрение ебать чувствую, что многие баги отсюда растут.

Пример второй: с неизменяемыми объектами (числа, строки) Тут всё проще, потому что их нахуй не изменишь.

x = 10  # Бирка `x` на числе 10.
y = x   # Бирка `y` на том же числе 10.
x = x + 5  # А вот тут ключевой момент! Ты не меняешь число 10. Ты создаёшь НОВОЕ число 15 и переклеиваешь бирку `x` на него.
print(x)   # 15
print(y)   # 10 — а бирка `y` так и осталась приклеена к старому доброму 10. Всё спокойно.

С неизменяемыми объектами можно не бздеть, их не испортишь.

А как тогда сделать нормальную, отдельную копию, а не эту манду с ушами?

  • Поверхностное копирование (copy()): Создаёт новый внешний контейнер, но внутрь кладёт ссылки на старые вложенные объекты. Как новый мешок, но с тем же старым дерьмом внутри.

    import copy
    list1 = [1, [2, 3]]  # Список, а внутри него ещё один список.
    list2 = copy.copy(list1)  # Делаем поверхностную копию.
    list1[0] = 100  # Меняем первый элемент — это ок, list2 не тронулся.
    list1[1].append(4)  # А вот тут добавляем во ВЛОЖЕННЫЙ список. Ёперный театр!
    print(list1)  # [100, [2, 3, 4]]
    print(list2)  # [1, [2, 3, 4]]  # И list2 тоже изменился! Потому что вложенный список у них общий!

    Видишь? copy() — это не панацея, если внутри есть изменяемая хрень.

  • Глубокое копирование (deepcopy()): Вот это уже серьёзно. Оно рекурсивно проходит по всей структуре и создаёт новые копии ВСЕГО, до самого дна. Полная независимость.

    list3 = copy.deepcopy(list1)  # Вот теперь делаем глубокую копию.
    list1[1].append(5)  # Снова лезем во вложенный список.
    print(list1)  # [100, [2, 3, 4, 5]]
    print(list3)  # [100, [2, 3, 4]]  # А list3 чист! Полностью отдельная сущность.

    deepcopy() — твой друг, когда нужно скопировать сложную структуру без сюрпризов.

Итог, чувак: Когда передаёшь изменяемый объект в функцию или просто делаешь b = a, помни — ты даёшь доступ к оригиналу. Если не хочешь неожиданностей — делай копию. Выбирай между copy (быстро, но опасно при вложенностях) и deepcopy (надёжно, но может быть медленнее). А то потом будешь сидеть и думать: «Какого хуя у меня данные попортились?».