Ответ
Изменяемые (mutable) объекты в Python — это те, состояние которых можно изменить после создания без создания нового объекта. Это важно понимать для корректной работы с присваиванием, передачей в функции и использованием в качестве аргументов по умолчанию.
Основные встроенные изменяемые типы:
-
Список (list): Упорядоченная коллекция.
my_list = [1, 2, 3] my_list[0] = 100 # Изменение элемента по индексу my_list.append(4) # Добавление в конец my_list.extend([5, 6]) # Расширение списка my_list.sort() # Сортировка на месте print(my_list) # [1, 2, 3, 4, 5, 6]? Нет! [100, 2, 3, 4, 5, 6] -
Словарь (dict): Коллекция пар ключ-значение.
my_dict = {'name': 'Alice', 'age': 30} my_dict['age'] = 31 # Изменение значения my_dict['city'] = 'Berlin' # Добавление новой пары my_dict.update({'job': 'Engineer'}) value = my_dict.pop('name') # Удаление с возвратом значения print(my_dict) # {'age': 31, 'city': 'Berlin', 'job': 'Engineer'} -
Множество (set): Неупорядоченная коллекция уникальных элементов.
my_set = {1, 2, 3} my_set.add(4) # Добавление элемента my_set.remove(2) # Удаление элемента (KeyError если нет) my_set.discard(10) # Безопасное удаление (не вызывает ошибки) my_set.clear() # Очистка всего множества print(my_set) # set()
Важные следствия изменяемости:
-
Присваивание по ссылке: При присваивании
new_list = my_listсоздается не копия, а новая ссылка на тот же объект.original = [1, 2, 3] alias = original alias.append(4) print(original) # [1, 2, 3, 4] - изменился и оригинал!Решение: использовать копирование (
copy()для поверхностной копии,deepcopy()из модуляcopyдля вложенных структур). -
Аргументы по умолчанию в функциях: Изменяемый объект как аргумент по умолчанию создается один раз при определении функции.
def append_to(element, target=[]): # ОПАСНО: target создается один раз! target.append(element) return target print(append_to(1)) # [1] print(append_to(2)) # [1, 2] - сюрприз!Правильный паттерн:
def append_to(element, target=None): if target is None: target = [] # Новый список при каждом вызове без аргумента target.append(element) return target
К другим изменяемым типам относятся bytearray, array.array, объекты collections (например, deque, Counter), а также экземпляры пользовательских классов (по умолчанию).
Ответ 18+ 🔞
А, ну вот, опять про эти ваши изменяемые объекты. Сидишь такой, пишешь код, всё вроде летит, а потом — бац! — и ты уже сам от себя охуел, потому что список, который ты вроде бы в другую переменную положил, внезапно мутировал, как мартышлюшка на банане. Давай разжую, как есть, без соплей.
Вот смотри. Изменяемые объекты — это такие штуки, которые можно поменять на месте, не создавая новый объект. Как будто ты перекрасил свою тачку — она та же, но цвет другой. С неизменяемыми так не прокатит, там придётся новую тачку покупать. И вот это «на месте» — оно и сила, и главная подстава, ёпта.
Основные бандиты, которые всё портят:
-
Список (list). Классика жанра, хитрая жопа. Создал — и понеслась.
my_list = [1, 2, 3] my_list[0] = 100 # Щёлк — и первый элемент уже не тот my_list.append(4) # Добавил с хвоста my_list.extend([5, 6]) # Впендюрил ещё кучу my_list.sort() # Перетряхнул на месте print(my_list) # Смотри-ка: [100, 2, 3, 4, 5, 6] -
Словарь (dict). Тут ключи — как сейфы, а значения — что в них лежит. И лежать там может что угодно, и меняться когда угодно.
my_dict = {'name': 'Алиса', 'age': 30} my_dict['age'] = 31 # Раз — и Алиса постарела на год my_dict['city'] = 'Берлин' # Добавил целый город, ядрёна вошь! my_dict.update({'job': 'Инженер'}) # А теперь и профессия есть value = my_dict.pop('name') # Выдернул имя и сохранил print(my_dict) # Глянь: {'age': 31, 'city': 'Берлин', 'job': 'Инженер'} -
Множество (set). Мешок с уникальными штуками. Кидаешь туда-сюда, а дубликаты сами исчезают.
my_set = {1, 2, 3} my_set.add(4) # Кинул четвёрку my_set.remove(2) # Выкинул двойку (если нет — будет скандал, KeyError) my_set.discard(10) # А вот это вежливое удаление — если нет, то и ладно my_set.clear() # Раз — и пустота. Всё вымел. print(my_set) # set()
А теперь, блядь, самое важное — где все обламываются:
-
Присваивание — это не копирование. Это как дать другу ключи от своей квартиры и сказать «поживи». А он там вечеринку устроит.
original = [1, 2, 3] alias = original # Это не копия! Это второй ключ от той же квартиры! alias.append(4) # Твой друг повесил новую картину. print(original) # [1, 2, 3, 4] — и в твоей-то квартире она тоже висит! Вот же ж пиздец.Решение? Копировать, чёрт возьми!
.copy()для простого, а если там внутри ещё списки сидят —deepcopy()из модуляcopy, чтоб наверняка. -
Аргументы по умолчанию в функциях — это ловушка для молодых. Тут волнение ебать, потому что список как значение по умолчанию создаётся один раз, когда функцию определяют, а не когда вызывают.
def append_to(element, target=[]): # ОПАСНОСТЬ! target родился тут и будет жить вечно. target.append(element) return target print(append_to(1)) # [1] — вроде норм. print(append_to(2)) # [1, 2] — а вот нихуя себе! Он же помнит прошлый вызов!Как делать правильно? Всегда
Noneпо умолчанию, а внутри проверяй.def append_to(element, target=None): if target is None: # Вот тут доверия ебать ноль к тому, что тебе передали. target = [] # Создаём новый, чистенький список каждый раз. target.append(element) return target
Короче, запомни: с изменяемыми типами сила — в возможности всё менять быстро. Но и ответственность, блядь, овердохуища — нужно следить, где твои ссылки гуляют и кто в твоих списках ковыряется. А то будет вам не хиросима, а настоящий нигерсраки в коде.