Ответ
В 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 (надёжно, но может быть медленнее). А то потом будешь сидеть и думать: «Какого хуя у меня данные попортились?».