Ответ
В Python разделение на изменяемые (mutable) и неизменяемые (immutable) типы — это фундаментальная концепция, влияющая на поведение объектов, передачу аргументов в функции и управление памятью.
Неизменяемые типы (immutable): int, float, str, tuple, bool, frozenset, bytes.
- После создания объект нельзя изменить. Любая операция, которая выглядит как изменение, на самом деле создает новый объект.
- Это делает их потокобезопасными и предсказуемыми при использовании в качестве ключей словаря.
a = "Hello" b = a # `b` ссылается на тот же объект, что и `a` a += " World" # Создается НОВЫЙ строковый объект для `a` print(a) # 'Hello World' print(b) # 'Hello' (старый объект не изменился)
Изменяемые типы (mutable): list, dict, set, bytearray, пользовательские классы (по умолчанию).
- Содержимое объекта можно изменить после создания, не создавая новый объект.
- Требуют осторожности, особенно при передаче в функции или использовании значений по умолчанию.
def append_to_list(item, target_list=[]): # ОПАСНО: изменяемый аргумент по умолчанию! target_list.append(item) return target_list
Лучшая практика — использовать None
def append_to_list_safe(item, target_list=None): if target_list is None: target_list = [] target_list.append(item) return target_list
**Практическое следствие:** При передаче неизменяемого объекта в функцию вы не можете изменить оригинал. При передаче изменяемого — можете, что может быть как полезным (изменение коллекции на месте), так и источником трудноуловимых багов. Ответ 18+ 🔞
А, слушай, вот эта тема с изменяемыми и неизменяемыми типами в Python — это, блядь, основа основ, но её так часто на раз-два проезжают, а потом люди охуевают от багов. Ёпта, давай разжую.
Представь, что у тебя есть два вида штук. Первые — как каменные скрижали, immutable. Написал a = "Привет" — и всё, высек в граните. int, float, строка, кортеж — они все такие. Попробуй изменить — нихуя не выйдет. Вместо этого Python, такой хитрая жопа, создаст тебе абсолютно новый кусок гранита, а старый так и будет лежать.
a = "Hello"
b = a # `b` — это просто табличка, указывающая на тот же самый камень
a += " World" # БАЦ! Высекаем новый камень с новой надписью для `a`
print(a) # 'Hello World' (новый камень)
print(b) # 'Hello' (старый камень, на который всё ещё смотрит `b`, не тронут)
Вот это «создаётся новый объект» — это, ёбаный насос, ключевой момент. Поэтому их и безопасно использовать как ключи в словарях — они не поменяются у тебя под носом.
А теперь вторые — изменяемые, mutable. Это, сука, как пластилин или глина. list, dict, set. Слепил список — и потом можешь его мять, добавлять, удалять, и это всё тот же кусок глины в памяти.
И вот тут начинается пиздец, если не понимать. Самый классический косяк, про который легенды ходят — изменяемый аргумент по умолчанию.
def append_to_list(item, target_list=[]): # ОПАСНОСТЬ, ЕБАНОЕ МАТЕРНОЕ СЛОВО!
target_list.append(item)
return target_list
Смотри, что происходит. Эта пустая скобка [] создаётся один раз, в момент определения функции. Не каждый раз при вызове! Один раз! И потом все вызовы без аргумента target_list будут тыкать в один и тот же список. Это пиздец как неочевидно.
print(append_to_list(1)) # [1]
print(append_to_list(2)) # А тут будет [1, 2], а не [2]! Сам от себя охуел?
Лучшая практика — делать через None. Просто запомни, как «Отче наш»:
def append_to_list_safe(item, target_list=None):
if target_list is None:
target_list = [] # А вот тут новый список создаётся КАЖДЫЙ РАЗ при вызове
target_list.append(item)
return target_list
Итоговая мысль, ёпта: Когда ты передаёшь неизменяемый объект в функцию — ты даёшь ей копию надписи на камне. Оригинал не тронешь. Передаёшь изменяемый — ты даёшь ей сам кусок пластилина. И функция может его изменить для всех, кто на него смотрит. Это может быть охуенно полезно (чтобы не копировать огромные структуры), а может устроить тебе такую хиросиму с багами, что волосы дыбом встанут. Просто знай, с чем работаешь.