← Назад к вопросам

Почему неизменяемые типы так называются?

2.0 Middle🔥 181 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему неизменяемые типы так называются?

Неизменяемые типы (immutable types) называются так потому, что их содержимое нельзя изменить после создания. Давайте разберёмся почему это важно и как это устроено.

1. Определение неизменяемости

Неизменяемый (immutable) тип — это объект, который не может быть изменён после создания. Если нужно «изменить» значение, создаётся новый объект.

# НЕИЗМЕНЯЕМЫЕ типы в Python
x = 5  # int
y = x
y = 10  # Создалось НОВОЕ число 10, x не изменился
print(x)  # 5 (не изменилось!)

name = "Alice"  # str
name[0] = "B"  # TypeError! Строку нельзя изменить
name = "Bob"   # Создалась НОВАЯ строка

t = (1, 2, 3)  # tuple
t[0] = 10      # TypeError! Кортеж нельзя изменить

# ИЗМЕНЯЕМЫЕ типы
lst = [1, 2, 3]  # list
lst[0] = 10  # ✅ Работает! Список изменился
lst.append(4)  # ✅ Работает! Список изменился

d = {'a': 1}  # dict
d['a'] = 2  # ✅ Работает! Словарь изменился

2. Почему это называется так?

Слово "immutable" происходит из латыни: "im-" (не) + "mutabilis" (изменяемый). Буквально — "не изменяемый".

Это название отражает самую суть: объект не может мутировать (меняться в своём содержимом). Вместо мутации создаётся новый объект.

Аналогия из реальной жизни:

  • Изменяемый = вы покупаете пальто, портной его подшивает (объект изменился)
  • Неизменяемый = вы покупаете пальто, оно вам не подходит → вы продаёте его и покупаете другое (объект не изменился, создался новый)

3. Неизменяемые типы в Python

# Примеры неизменяемых типов
int, float, complex  # Числа
bool                  # Логический тип
str                   # Строка
tuple                 # Кортеж (если содержит только неизменяемые элементы)
frozenset             # Неизменяемое множество
bytes                 # Байтовая строка
None                  # Значение отсутствия

Доказательство неизменяемости:

# Смотрим id объекта (адрес в памяти)
x = 5
print(id(x))  # 140734873000040

x = 10
print(id(x))  # 140734873000208 (ДРУГОЙ адрес! Новый объект!)

# Со строками
s = "hello"
print(id(s))  # 140734805670896

s = "world"
print(id(s))  # 140734805671184 (Другой адрес!)

s = s + "!"   # s + "!" создаёт НОВУЮ строку
print(id(s))  # 140734805671408 (Опять новый адрес!)

4. Где Python кэширует неизменяемые объекты?

Чтобы сэкономить память, Python кэширует часто используемые неизменяемые объекты:

# Маленькие integers кэшируются (-5 до 256)
a = 5
b = 5
print(a is b)  # True! Один и тот же объект в памяти

c = 257
d = 257
print(c is d)  # False! Разные объекты (вне кэша)

# Строки кэшируются, если они выглядят как идентификаторы
s1 = "hello"
s2 = "hello"
print(s1 is s2)  # True! Интернирование строк

s3 = "hello world"  # С пробелом
s4 = "hello world"
print(s3 is s4)  # False! Не интернируется

5. Почему неизменяемость важна?

a) Безопасность в многопоточности

# НЕИЗМЕНЯЕМОЕ значение — безопасно в многопоточности
import threading

shared_tuple = (1, 2, 3)

def read_tuple():
    for _ in range(1000000):
        x = shared_tuple[0]  # Безопасно без lock!

threads = [threading.Thread(target=read_tuple) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()

# ИЗМЕНЯЕМОЕ значение — нужен lock
shared_list = [1, 2, 3]
lock = threading.Lock()

def modify_list():
    for _ in range(1000000):
        with lock:  # ⚠️ Нужен lock для безопасности
            shared_list[0] = 1

b) Использование как ключи в словаре

# ✅ Неизменяемое можно использовать как ключ
d = {
    (1, 2): "tuple key",
    "name": "string key",
    42: "int key"
}
print(d[(1, 2)])  # Works!

# ❌ Изменяемое нельзя использовать как ключ
d = {
    [1, 2]: "list key"  # TypeError! unhashable type: 'list'
}

Почему? Хэштаблица нужна гарантия, что ключ не изменится. Если ключ изменится, его хэш поменяется, и поиск сломается.

c) Кэширование и оптимизация

# Функция может кэшировать результаты для неизменяемых аргументов
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Это работает, потому что int — неизменяемый!
print(fibonacci(100))  # Быстро за счёт кэша

# С изменяемым аргументом (список) это не сработает
@lru_cache(maxsize=128)
def process_list(items: list) -> int:  # TypeError! unhashable
    pass

d) Предсказуемость и отсутствие side effects

# Неизменяемый объект — предсказуемый результат
def format_string(s: str) -> str:
    return s.upper()

original = "hello"
result = format_string(original)
print(original)  # "hello" — не изменилось!

# Изменяемый объект — может неожиданно изменить исходные данные
def modify_list(lst: list) -> list:
    lst.append(999)  # ⚠️ Side effect!
    return lst

original = [1, 2, 3]
result = modify_list(original)
print(original)  # [1, 2, 3, 999] — изменилось!

6. Как работает неизменяемость на уровне памяти?

# Простое число
x = 5  # x указывает на объект int со значением 5
x = 10  # x указывает на ДРУГОЙ объект int со значением 10
        # Старый объект (5) всё ещё в памяти, но на него нет ссылок
        # Garbage collector удалит его

# Строка
s = "hello"  # s указывает на объект str "hello"
s = s + "!"  # s теперь указывает на НОВЫЙ объект str "hello!"
             # Старый объект "hello" удалится, если на него нет других ссылок

# Кортеж
t = (1, 2, 3)  # t указывает на объект tuple с тремя элементами
t = (1, 2, 3, 4)  # t теперь указывает на НОВЫЙ кортеж

7. Неизменяемость в контексте функционального программирования

Идея неизменяемости исходит из функционального программирования, где:

  • Функции не должны менять входные данные (pure functions)
  • Вместо изменения создаётся новое значение
  • Это упрощает рассуждение о коде и тестирование
# Функциональный стиль
def add_item(items: tuple, item) -> tuple:
    return items + (item,)  # Новый кортеж, исходный не изменился

original = (1, 2, 3)
new = add_item(original, 4)
print(original)  # (1, 2, 3) — не изменился
print(new)       # (1, 2, 3, 4)

# Императивный стиль (изменяет список)
def add_item_imperative(items: list, item):
    items.append(item)  # Изменяет исходный список!

original = [1, 2, 3]
add_item_imperative(original, 4)
print(original)  # [1, 2, 3, 4] — изменился!

Итог

Неизменяемые типы называются immutable (не изменяемые), потому что их содержимое не может быть изменено после создания. Это обеспечивает:

  • Безопасность в многопоточности
  • Возможность использования как ключей в словарях
  • Предсказуемость кода
  • Возможность кэширования
Почему неизменяемые типы так называются? | PrepBro