← Назад к вопросам
Будет ли класс иммутабельным (неизменяемым), если в нем находится атрибут?
1.8 Middle🔥 61 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Иммутабельность в Python: классы и атрибуты
Краткий ответ
Нет, класс не будет иммутабельным только потому, что содержит атрибуты. Иммутабельность — это свойство экземпляра, которое должно быть явно реализовано. По умолчанию экземпляры классов в Python — мутабельные (изменяемые).
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 10 # можем изменить атрибут
print(p.x) # 10 — класс НЕ иммутабельный
Что такое иммутабельность?
Объект иммутабельный, если его состояние не может быть изменено после создания. Это ключевой концепт в функциональном программировании и обеспечивает:
- Thread-safety — можно безопасно использовать в многопоточных приложениях
- Cacheable — можно кешировать (напр., использовать в качестве ключей в dict)
- Predictability — код более предсказуем, нет скрытых побочных эффектов
Иммутабельные встроенные типы в Python
# ✅ Иммутабельные типы
int_val = 42
int_val = 43 # новый объект, не изменение
str_val = "hello"
str_val = str_val + " world" # новая строка, не изменение
tuple_val = (1, 2, 3)
# tuple_val[0] = 10 # TypeError!
# ❌ Мутабельные типы
list_val = [1, 2, 3]
list_val[0] = 10 # изменение на месте ✓
dict_val = {'a': 1}
dict_val['a'] = 2 # изменение на месте ✓
set_val = {1, 2, 3}
set_val.add(4) # изменение на месте ✓
Способ 1: Использовать __slots__ (базовый)
__slots__ запрещает добавление новых атрибутов, но не делает класс иммутабельным:
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x) # 1
# Можем изменить существующий атрибут
p.x = 10
print(p.x) # 10 — всё ещё мутабельно!
# Но не можем добавить новый
# p.z = 5 # AttributeError
Способ 2: Использовать frozen dataclass
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
print(p.x) # 1
# Попытка изменить атрибут
# p.x = 10 # FrozenInstanceError: cannot assign to field 'x'
# Можем использовать как ключ в dict
points = {p: "point_1"}
print(points[Point(1, 2)]) # "point_1"
# Можем использовать в set
point_set = {Point(1, 2), Point(3, 4)}
print(Point(1, 2) in point_set) # True
Способ 3: Использовать NamedTuple
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
p = Point(1, 2)
print(p.x) # 1
# Попытка изменить
# p.x = 10 # AttributeError: can't set attribute
# Это кортеж, поэтому полностью иммутабельный
print(hash(p)) # можем хешировать
print(isinstance(p, tuple)) # True
Способ 4: __setattr__ и __delattr__
class ImmutablePoint:
def __init__(self, x, y):
object.__setattr__(self, 'x', x)
object.__setattr__(self, 'y', y)
def __setattr__(self, name, value):
raise AttributeError(f"Cannot modify {name}")
def __delattr__(self, name):
raise AttributeError(f"Cannot delete {name}")
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = ImmutablePoint(1, 2)
print(p) # Point(1, 2)
# Попытка изменить
# p.x = 10 # AttributeError: Cannot modify x
# Попытка удалить
# del p.x # AttributeError: Cannot delete x
Сравнение подходов
| Подход | Плюсы | Минусы | Hash | Equals |
|---|---|---|---|---|
frozen=True dataclass | Простой, модерный | Python 3.7+ | ✓ | ✓ |
| NamedTuple | Лёгкий, tuple-like | Менее гибкий | ✓ | ✓ |
__setattr__ перегрузка | Полный контроль | Многословный | ✗ нужно явно | ✗ нужно явно |
__slots__ | Экономия памяти | Не иммутабельный | ✗ нужно явно | ✗ нужно явно |
Проблема: иммутабельность поверхностная
Если атрибут содержит мутабельный объект, внешний состояние всё равно может быть изменено:
from dataclasses import dataclass
@dataclass(frozen=True)
class Box:
items: list # мутабельный список
box = Box([1, 2, 3])
print(box.items) # [1, 2, 3]
# Хотя box иммутабельная, мы можем изменить список внутри
box.items.append(4)
print(box.items) # [1, 2, 3, 4] — изменилось!
# Это называется "shallow immutability" (поверхностная иммутабельность)
Решение: глубокая иммутабельность
from dataclasses import dataclass, field
from copy import deepcopy
@dataclass(frozen=True)
class Box:
items: tuple # используем tuple вместо list
box = Box((1, 2, 3))
print(box.items) # (1, 2, 3)
# Теперь действительно иммутабельно
# box.items.append(4) # AttributeError: 'tuple' object has no attribute 'append'
# Или используем post_init для копирования
@dataclass(frozen=True)
class SafeBox:
_items: list = field(default_factory=list, init=False, repr=False)
def __post_init__(self):
# Сохраняем копию
items = getattr(self, '__dict__', {}).get('items', [])
object.__setattr__(self, '_items', tuple(items))
@property
def items(self) -> tuple:
return self._items
Практический пример: ValueObject в DDD
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class Email:
"""Иммутабельный value object для email"""
value: str
def __post_init__(self):
if '@' not in self.value:
raise ValueError("Invalid email")
def __str__(self):
return self.value
@dataclass(frozen=True)
class User:
name: str
email: Email
# Использование
try:
invalid_email = Email("invalid")
except ValueError:
print("Invalid email")
email = Email("user@example.com")
user = User("John", email)
print(user) # User(name='John', email=user@example.com)
# Можем использовать как ключ
users_map = {user: "admin"}
print(users_map[User("John", Email("user@example.com"))]) # "admin"
# Изменение защищено
# user.email = Email("other@example.com") # FrozenInstanceError
Иммутабельность vs наследование
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
# Попытка создать подкласс
@dataclass(frozen=True)
class ColoredPoint(Point):
color: str
p = ColoredPoint(1, 2, "red")
print(p) # ColoredPoint(x=1, y=2, color='red')
# Всё ещё иммутабельно
# p.color = "blue" # FrozenInstanceError
Итоги: как сделать класс иммутабельным
✅ Используй одно из:
@dataclass(frozen=True)— самый простой и рекомендуемый способNamedTuple— если нужно наследоваться от tuple__setattr__/__delattr__— если нужен полный контроль
⚠️ Помни:
- Просто наличие атрибутов не делает класс иммутабельным
- Иммутабельность по умолчанию поверхностная (shallow)
- Используй мутабельные типы (list, dict) с осторожностью в frozen объектах
- Иммутабельные объекты можно хешировать и использовать в dict/set
# ✅ Рекомендуемый способ
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
# p.x = 10 # FrozenInstanceError
print(hash(p)) # можем использовать как ключ