Изменяемые и неизменяемые типы в Python: понятное руководство с примерами и ошибками...

Изменяемые и неизменяемые типы в Python: понятное руководство с примерами и ошибками новичков

Изменяемые и неизменяемые типы в 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 с Нуля до Гуру» — посмотреть программу и первый урок.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий