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

Что такое неизменяемый объект в Python?

2.2 Middle🔥 131 комментариев
#Python Core#Soft Skills

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

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

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

Неизменяемые объекты в Python (Immutable Objects)

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

Какие объекты неизменяемые?

# ✓ НЕИЗМЕНЯЕМЫЕ (Immutable):
int         # 5, -10, 1000000
float       # 3.14, 2.0
str         # "hello", "Python"
tuple       # (1, 2, 3), ("a", "b")
bool        # True, False
frozenset   # frozenset({1, 2, 3})
bytes       # b"data"
None        # None

# ❌ ИЗМЕНЯЕМЫЕ (Mutable):
list        # [1, 2, 3]
dict        # {"key": "value"}
set         # {1, 2, 3}
bytearray   # bytearray(b"data")
объекты классов  # user = User()

Демонстрация: строки

# Строки НЕИЗМЕНЯЕМЫ
s = "hello"
print(id(s))  # 140234567890000

# Попытка "изменить"
s = s + " world"  # На самом деле создаёт НОВУЮ строку
print(id(s))  # 140234567890100 — ДРУГОЙ id!

# Старая строка "hello" всё ещё в памяти
# Сборка мусора удалит её позже

# Это то же самое что:
s = "hello"
old_id = id(s)
s = "hello world"  # Новый объект
new_id = id(s)
print(old_id == new_id)  # False

Попытка модифицировать

# Если бы строки были изменяемы (чего нет):
s = "hello"
s[0] = "H"  # ❌ TypeError: 'str' object does not support item assignment

# Правильный способ:
s = "hello"
s = s[0].upper() + s[1:]  # Создаёт новую строку
print(s)  # "Hello"

# Или используем метод (который тоже создаёт новую):
s = "hello"
s = s.replace("h", "H")  # Новая строка
print(s)  # "Hello"

Кортежи (tuples)

# Кортежи неизменяемы
t = (1, 2, 3)
print(id(t))  # 140234567890000

# Попытка изменить элемент:
t[0] = 5  # ❌ TypeError: 'tuple' object does not support item assignment

# Но если элементы изменяемы:
t = ([1, 2], [3, 4])
t[0][0] = 999  # ✓ Работает! Список внутри кортежа изменился
print(t)  # ([999, 2], [3, 4])
# Сам кортеж остался неизменяемым (тот же id)
# Но элемент (список) изменился

Пример: список vs кортеж

# СПИСОК (изменяемый)
list_obj = [1, 2, 3]
original_id = id(list_obj)
list_obj.append(4)  # Изменяем на месте
print(id(list_obj) == original_id)  # True (ТОТ ЖЕ объект)
print(list_obj)  # [1, 2, 3, 4]

# КОРТЕЖ (неизменяемый)
tuple_obj = (1, 2, 3)
original_id = id(tuple_obj)
new_tuple = tuple_obj + (4,)  # Создаёт НОВЫЙ кортеж
print(id(new_tuple) == original_id)  # False (другой объект)
print(tuple_obj)  # (1, 2, 3) — не изменился
print(new_tuple)  # (1, 2, 3, 4)

Словари из фильма о производительности

# Словарь изменяемый
user = {"name": "Alice", "age": 30}
original_id = id(user)

# Можем менять значения
user["age"] = 31  # Тот же объект
print(id(user) == original_id)  # True

# Но если создаём новый словарь
user = {"name": "Alice", "age": 31}
print(id(user) == original_id)  # False — новый объект

Копирование и неизменяемость

# Неизменяемые объекты
a = "hello"
b = a  # Просто ссылка на тот же объект
print(id(a) == id(b))  # True

# Если переприсвоить:
a = "world"
print(id(a) == id(b))  # False — новый объект
print(b)  # "hello" — не изменилась

# Изменяемые объекты
list_a = [1, 2, 3]
list_b = list_a  # Ссылка на тот же объект
print(id(list_a) == id(list_b))  # True

list_a.append(4)  # Изменяем
print(id(list_a) == id(list_b))  # Всё ещё True!
print(list_b)  # [1, 2, 3, 4] — изменилась!

# Правильное копирование:
list_b = list_a.copy()  # Или list(list_a)
list_a.append(5)
print(list_a)  # [1, 2, 3, 4, 5]
print(list_b)  # [1, 2, 3, 4] — не изменилась

Практический пример: функции

# Неизменяемые аргументы безопасны
def process_string(s: str) -> str:
    """Строка не может быть изменена внутри функции."""
    s = s + " processed"  # Создаёт новую строку
    return s

original = "hello"
result = process_string(original)
print(original)  # "hello" — не изменилась
print(result)    # "hello processed"

# Изменяемые аргументы НЕ безопасны
def add_item(items: list, item: str):
    """Список МОЖЕТ быть изменён!"""
    items.append(item)  # Изменяет оригинальный список
    return items

my_list = ["a", "b"]
result = add_item(my_list, "c")
print(my_list)  # ["a", "b", "c"] — ИЗМЕНИЛСЯ!
print(result)   # ["a", "b", "c"]

# Правильно: не менять входной параметр
def add_item_safe(items: list, item: str) -> list:
    """Создаёт новый список."""
    return items + [item]

my_list = ["a", "b"]
result = add_item_safe(my_list, "c")
print(my_list)  # ["a", "b"] — не изменился
print(result)   # ["a", "b", "c"]

Хеширование и словари

# Неизменяемые объекты хешируемы (hashable)
# Их можно использовать как ключи словарей

# ✓ Строка как ключ
user = {"name": "Alice", "age": 30}  # Строки это ключи

# ✓ Кортеж как ключ
coordinates = {(0, 0): "origin", (1, 2): "point A"}

# ✓ Целое число как ключ
ranking = {1: "Gold", 2: "Silver", 3: "Bronze"}

# ❌ Список как ключ — ошибка!
user = {["Alice"]: 30}  # TypeError: unhashable type: 'list'

# ❌ Словарь как ключ — ошибка!
user = {{"name": "Alice"}: 30}  # TypeError: unhashable type: 'dict'

# Почему? Потому что изменяемые объекты могут меняться
# После добавления в словарь ключ изменится
# → словарь развалится

# Решение: используй frozenset вместо set
frozenset_key = frozenset([1, 2, 3])
data = {frozenset_key: "immutable set"}  # ✓ Работает

Performance: строк неизменяемы

# НЕПРАВИЛЬНО: конкатенация строк в цикле
result = ""
for i in range(1000000):
    result += str(i)  # ❌ Создаёт 1M новых строк!
    # Очень медленно!

# ПРАВИЛЬНО: используй список
result = []
for i in range(1000000):
    result.append(str(i))  # Добавляет в список
final = "".join(result)  # Один раз создаёт большую строку

# ПРАВИЛЬНО: используй f-strings или format
data = []
for i in range(100):
    data.append(f"Item {i}")  # Оптимизированный способ

Default mutable arguments (ЛОВУШКА!)

# ❌ ОШИБКА: изменяемый аргумент по умолчанию
def add_item(item, items=[]):  # ← items это общий объект!
    items.append(item)
    return items

result1 = add_item("a")  # ["a"]
result2 = add_item("b")  # ["a", "b"] — НЕ ["b"]!
print(result1)  # ["a", "b"] — изменился!

# Список создаётся один раз при определении функции
# И переиспользуется для всех вызовов

# ✓ ПРАВИЛЬНО: None по умолчанию
def add_item_safe(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

result1 = add_item_safe("a")  # ["a"]
result2 = add_item_safe("b")  # ["b"]

# Или ещё лучше: не менять входные параметры
def add_item_better(item, items=None):
    if items is None:
        items = []
    return items + [item]  # Новый список

Создание неизменяемых классов

from dataclasses import dataclass
from typing import Tuple

# СПОСОБ 1: Использовать frozen dataclass
@dataclass(frozen=True)  # ← frozen=True делает неизменяемым
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
print(hash(p))  # Можем хешировать

# p.x = 3.0  # ❌ FrozenInstanceError

# СПОСОБ 2: Использовать NamedTuple
from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float

p = Point(1.0, 2.0)
print(hash(p))  # Хешируемый
# p.x = 3.0  # ❌ AttributeError

# СПОСОБ 3: Переопределить __setattr__
class ImmutablePoint:
    def __init__(self, x: float, y: float):
        object.__setattr__(self, 'x', x)
        object.__setattr__(self, 'y', y)
    
    def __setattr__(self, name, value):
        raise TypeError(f"Cannot modify {name}")
    
    def __hash__(self):
        return hash((self.x, self.y))

p = ImmutablePoint(1.0, 2.0)
print(hash(p))  # ✓ Работает
# p.x = 3.0  # ❌ TypeError

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

# Неизменяемые объекты можно использовать как ключи
users_by_name = {
    "Alice": User(name="Alice", age=30),
    "Bob": User(name="Bob", age=25),
}

# Кортежи как ключи для многомерных данных
temperature_data = {
    ("New York", "2024-01-01"): 2.5,
    ("New York", "2024-01-02"): 3.1,
    ("Los Angeles", "2024-01-01"): 18.3,
}

temp_ny = temperature_data[("New York", "2024-01-01")]
print(temp_ny)  # 2.5

# frozenset для наборов как ключей
friends_of = {
    frozenset(["Alice", "Bob"]): "best friends",
    frozenset(["Charlie", "Diana"]): "colleagues",
}

Ключевые моменты

  • Неизменяемые объекты (int, str, tuple) нельзя менять
  • Попытка изменить создаёт новый объект с новым id
  • Изменяемые объекты (list, dict, set) можно менять на месте
  • Копирование критично для изменяемых объектов
  • Неизменяемые хешируемы → можно использовать как ключи
  • Default mutable arguments это частая ошибка
  • Performance конкатенация строк медленная → используй join()
  • Frozen dataclass способ создать неизменяемые объекты
Что такое неизменяемый объект в Python? | PrepBro