Ответ
Наличие атрибутов в классе не делает его автоматически иммутабельным. Иммутабельность (неизменяемость) объекта в Python определяется тем, можно ли изменить его состояние (значения его атрибутов) после создания.
Почему иммутабельность важна:
- Предсказуемость: Состояние объекта не меняется, что упрощает отладку и понимание кода.
- Потокобезопасность: Иммутабельные объекты по своей природе потокобезопасны, так как их не нужно защищать от одновременного изменения разными потоками.
- Использование в качестве ключей: Иммутабельные объекты могут быть использованы как ключи в словарях (
dict) или элементы в множествах (set), поскольку их хеш-значение не меняется.
Как добиться иммутабельности в Python:
-
Запрет изменения атрибутов:
- Переопределение метода
__setattr__для запрета присваивания значений атрибутам после инициализации. - Использование
__slots__для предотвращения динамического добавления новых атрибутов и оптимизации памяти.
- Переопределение метода
-
Использование
dataclassesсfrozen=True(Python 3.7+): Это наиболее современный и простой способ создания иммутабельных классов.
Пример изменяемого (Mutable) класса:
class MutablePoint:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
point = MutablePoint(1, 2)
print(f"Исходная точка: ({point.x}, {point.y})") # Вывод: (1, 2)
point.x = 10 # Атрибут можно изменить
print(f"Измененная точка: ({point.x}, {point.y})") # Вывод: (10, 2)
Пример иммутабельного (Immutable) класса (ручная реализация):
class ImmutablePoint:
__slots__ = ('_x', '_y') # Используем __slots__ и префикс '_' для внутренних атрибутов
def __init__(self, x: int, y: int):
# Для инициализации атрибутов в иммутабельном классе нужно обойти __setattr__
# или использовать super().__setattr__
super().__setattr__('_x', x)
super().__setattr__('_y', y)
@property
def x(self) -> int:
return self._x
@property
def y(self) -> int:
return self._y
def __setattr__(self, name, value):
# Запрещаем изменение любых атрибутов после инициализации
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}' or is immutable")
def __repr__(self):
return f"ImmutablePoint(x={self.x}, y={self.y})"
point = ImmutablePoint(1, 2)
print(f"Исходная точка: {point}") # Вывод: ImmutablePoint(x=1, y=2)
try:
point.x = 10 # Попытка изменить атрибут вызовет ошибку
except AttributeError as e:
print(f"Ошибка при попытке изменения: {e}") # Вывод: Ошибка при попытке изменения: 'ImmutablePoint' object has no attribute 'x' or is immutable
Пример иммутабельного класса с dataclasses (рекомендуемый способ):
from dataclasses import dataclass
@dataclass(frozen=True) # frozen=True делает класс иммутабельным
class FrozenPoint:
x: int
y: int
point = FrozenPoint(1, 2)
print(f"Исходная точка: {point}") # Вывод: FrozenPoint(x=1, y=2)
try:
point.x = 10 # Попытка изменить атрибут вызовет ошибку
except Exception as e: # dataclasses.FrozenInstanceError
print(f"Ошибка при попытке изменения: {type(e).__name__}: {e}") # Вывод: FrozenInstanceError: cannot assign to field 'x'
Таким образом, иммутабельность класса в Python достигается не просто наличием атрибутов, а строгим контролем над их изменением после создания объекта.