Изменяемые и неизменяемые типы в Python: понятное руководство с примерами и ошибками новичков
Запрос «изменяемые и неизменяемые типы в Python» регулярно ищут начинающие разработчики. И не зря: понимание иммутабельности помогает избегать коварных багов и писать предсказуемый код. Ниже — краткая теория и практические примеры, которые быстро всё расставят по местам.
Что такое изменяемые и неизменяемые типы в Python
Неизменяемые (immutable): int, float, bool, str, tuple, bytes, frozenset, NoneType. Их состояние после создания изменить нельзя — любые «изменения» создают новый объект.
Изменяемые (mutable): list, dict, set, bytearray и большинство пользовательских объектов. Их можно менять «на месте» — добавлять элементы, изменять значения и т. п.
Идентичность, равенство и хэш: что происходит под капотом
В Python есть три связанных понятия:
- Идентичность — функция
id(obj)и операторisпроверяют, один и тот же ли это объект в памяти. - Равенство — оператор
==сравнивает значения (содержимое). - Хэш —
hash(obj)нужен для словарей и множеств; требует неизменяемости содержимого.
a = [1, 2, 3]
b = a
a.append(4)
print(a, b) # [1, 2, 3, 4] [1, 2, 3, 4]
print(id(a) == id(b)) # True — это один и тот же список
x = 10
y = x
x += 5
print(x, y) # 15 10 — создался новый int для x
print(id(x) == id(y)) # Обычно False
s1 = "hello"
s2 = "he" + "llo"
print(s1 == s2, s1 is s2) # True и (True или False) — интернирование строк зависит от интерпретатора
print(hash((1, 2, 3))) # кортеж хешируем, если он из неизменяемых элементов
# hash([1, 2, 3]) # TypeError: unhashable type: 'list'
Вывод: изменяемые объекты обычно сохраняют свой id при модификации (меняется содержимое), а неизменяемые создают новый объект при любом «изменении значения».
Как иммутабельность влияет на функции
Аргументы в Python передаются по ссылке на объект. Поэтому изменение изменяемого аргумента внутри функции влияет на исходный объект; с неизменяемыми — нет (создаётся новый объект, ссылка в вызывающем коде не меняется).
def add_item(lst):
lst.append(42)
def inc(n):
n += 1
return n
nums = [1, 2]
add_item(nums)
print(nums) # [1, 2, 42] — изменили исходный список
value = 10
print(inc(value), value) # 11 10 — исходное число не изменилось
Коварство аргументов по умолчанию
Значения по умолчанию вычисляются один раз — при определении функции. Если это изменяемый объект, он будет «копиться» между вызовами.
def append_item(x, store=[]):
store.append(x)
return store
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] — неожиданно!
# Правильный шаблон
def append_item_safe(x, store=None):
if store is None:
store = []
store.append(x)
return store
Неизменяемость и ключи словаря/элементы множества
Ключи словаря и элементы множества должны быть хешируемыми, то есть иметь неизменяемое содержимое. Поэтому подойдут str, int, tuple (из неизменяемых элементов), frozenset, но не list или dict.
d = {}
d[(1, 2)] = "ok" # кортеж — хешируемый
# d[[1, 2]] = "boom" # TypeError: unhashable type: 'list'
s = set()
# s.add([1, 2]) # TypeError
s.add((1, 2)) # ok
s.add(frozenset({1, 2})) # тоже ok
# Важно: кортеж хешируем, только если все его элементы хешируемы
# hash((1, [2])) # TypeError: unhashable type: 'list'
Копирование: поверхностное и глубокое
При работе с изменяемыми коллекциями важно понимать разницу между поверхностной и глубокой копией.
import copy
a = [[1], [2]]
b = a.copy() # поверхностная копия (или list(a))
c = copy.deepcopy(a) # глубокая копия
a[0].append(99)
print(a) # [[1, 99], [2]]
print(b) # [[1, 99], [2]] — вложенные списки общие
print(c) # [[1], [2]] — полностью независимая структура
Типичные ошибки с изменяемыми типами
Ещё одна частая ловушка — умножение списков со вложенными структурами.
m1 = [[0]*3 for _ in range(3)] # правильно — создаём независимые строки
m2 = [[0]*3]*3 # опасно — три ссылки на один и тот же список
m1[0][0] = 1
m2[0][0] = 1
print(m1) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
print(m2) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
Практические рекомендации
- Используйте неизменяемые типы (например,
tuple) там, где данные должны оставаться константными или быть ключами словаря. - Для аргументов по умолчанию не используйте изменяемые объекты; применяйте шаблон с
Noneи инициализацией внутри функции. - Передавайте во внешние API копии коллекций, если не хотите, чтобы их меняли.
- При копировании вложенных структур используйте
copy.deepcopy, если вам нужна полная независимость. - Проверяйте подозрительные баги через
id(): это быстро показывает, создаёте ли вы новый объект или мутируете старый.
Мини‑шпаргалка по изменяемости
- Неизменяемые: int, float, bool, str, tuple, bytes, frozenset, NoneType
- Изменяемые: list, dict, set, bytearray и большинство пользовательских классов
- Ключи словаря/элементы множества: только хешируемые (неизменяемые по содержимому)
Заключение
Иммутабельность — фундамент «питоновского» мышления. Понимая разницу между изменяемыми и неизменяемыми типами в Python, вы избежите загадочных побочных эффектов, сделаете код надёжнее и легче для тестирования. Хотите системно прокачаться и закрыть все пробелы в основах? Посмотрите программу и начните с сильной теории и практики в курсе: Пройти «Python с Нуля до Гуру» — посмотреть программу и первый урок.
-
Создано 16.03.2026 17:33:30
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.