Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Immutable Data Structures — Неизменяемые структуры данных
Неизменяемые (Immutable) структуры данных — это объекты, которые не могут быть изменены после создания. Вместо модификации исходного объекта, операции возвращают новый объект с изменёнными данными. Это фундаментальный принцип функционального программирования, обеспечивающий безопасность потоков и предсказуемость кода.
Встроенные неизменяемые типы Python
Python имеет несколько встроенных неизменяемых типов:
# 1. Строки (immutable)
s = 'Hello'
# s[0] = 'J' # TypeError: 'str' object does not support item assignment
new_s = s.replace('H', 'J') # Создаёт новую строку
print(new_s) # 'Jello'
# 2. Кортежи (immutable)
t = (1, 2, 3)
# t[0] = 10 # TypeError: 'tuple' object does not support item assignment
new_t = t + (4, 5) # Создаёт новый кортеж
print(new_t) # (1, 2, 3, 4, 5)
# 3. Числа (immutable)
x = 42
# x[0] = 5 # Невозможно (числа — скаляры)
new_x = x + 1 # Создаёт новое число
print(new_x) # 43
# 4. frozenset (неизменяемое множество)
fs = frozenset([1, 2, 3])
# fs.add(4) # AttributeError: 'frozenset' object has no attribute 'add'
new_fs = fs | frozenset([4, 5]) # Создаёт новое множество
print(new_fs) # frozenset({1, 2, 3, 4, 5})
# Изменяемые типы (для сравнения)
# Список (mutable)
lst = [1, 2, 3]
lst[0] = 10 # Изменяет список на месте
print(lst) # [10, 2, 3]
# Словарь (mutable)
d = {'a': 1}
d['a'] = 2 # Изменяет словарь на месте
print(d) # {'a': 2}
Создание собственных неизменяемых классов
from dataclasses import dataclass
from typing import NamedTuple
# Способ 1: dataclass с frozen=True
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0)
# p.x = 3.0 # FrozenInstanceError: cannot assign to field 'x'
# Способ 2: NamedTuple
class Location(NamedTuple):
latitude: float
longitude: float
loc = Location(37.7749, -122.4194)
print(loc) # Location(latitude=37.7749, longitude=-122.4194)
# loc.latitude = 37.0 # AttributeError: can't set attribute
# Способ 3: Пользовательский класс с __slots__
class Immutable:
__slots__ = ('_x', '_y') # Запретить добавление новых атрибутов
def __init__(self, x: int, y: int):
object.__setattr__(self, '_x', x)
object.__setattr__(self, '_y', y)
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __setattr__(self, name, value):
raise AttributeError('Cannot modify immutable object')
im = Immutable(10, 20)
print(im.x, im.y) # 10 20
# im.x = 30 # AttributeError: Cannot modify immutable object
Преимущества неизменяемости
1. Потокобезопасность (Thread Safety)
from threading import Thread
from dataclasses import dataclass
@dataclass(frozen=True)
class ImmutableUser:
id: int
name: str
# Безопасно использовать в разных потоках без мьютекса
user = ImmutableUser(1, 'Alice')
def thread_func():
print(user.name) # Никогда не изменится
threads = [Thread(target=thread_func) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
2. Кэширование и хеширование
from dataclasses import dataclass
@dataclass(frozen=True)
class UserKey:
id: int
email: str
# Можно использовать как ключ словаря (нужна хешируемость)
cache = {}
user_key = UserKey(1, 'alice@example.com')
cache[user_key] = {'name': 'Alice', 'age': 25}
# Повторное использование того же ключа
print(cache[user_key]) # {'name': 'Alice', 'age': 25}
# Это не работает с изменяемыми объектами
mutable_dict = {'id': 1, 'email': 'alice@example.com'}
# cache[mutable_dict] = ... # TypeError: unhashable type: 'dict'
3. Предсказуемость и отсутствие побочных эффектов
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
amount: float
currency: str
def add_tax(money: Money, tax_rate: float) -> Money:
"""Функция НЕ изменяет исходный объект"""
new_amount = money.amount * (1 + tax_rate)
return Money(new_amount, money.currency)
original = Money(100, 'USD')
with_tax = add_tax(original, 0.1)
print(original) # Money(amount=100, currency='USD') Не изменился!
print(with_tax) # Money(amount=110, currency='USD') Новый объект
Практический пример: неизменяемое состояние приложения
from dataclasses import dataclass
from typing import List, Tuple
from datetime import datetime
@dataclass(frozen=True)
class Task:
id: int
title: str
completed: bool
@dataclass(frozen=True)
class AppState:
tasks: Tuple[Task, ...] # Используем кортеж (immutable)
selected_task_id: int
last_update: datetime
# Функция для добавления задачи (возвращает новое состояние)
def add_task(state: AppState, task: Task) -> AppState:
new_tasks = state.tasks + (task,) # Создаём новый кортеж
return AppState(
tasks=new_tasks,
selected_task_id=state.selected_task_id,
last_update=datetime.now()
)
# Функция для завершения задачи
def complete_task(state: AppState, task_id: int) -> AppState:
new_tasks = tuple(
Task(t.id, t.title, True) if t.id == task_id else t
for t in state.tasks
)
return AppState(
tasks=new_tasks,
selected_task_id=state.selected_task_id,
last_update=datetime.now()
)
# Использование
initial_state = AppState(
tasks=(),
selected_task_id=0,
last_update=datetime.now()
)
state1 = add_task(initial_state, Task(1, 'Learn Python', False))
state2 = add_task(state1, Task(2, 'Learn FastAPI', False))
state3 = complete_task(state2, 1)
print(f'State 1: {len(state1.tasks)} tasks') # 1 task
print(f'State 2: {len(state2.tasks)} tasks') # 2 tasks
print(f'State 3: {len(state3.tasks)} tasks') # 2 tasks
print(f'Task 1 in state3: {state3.tasks[0].completed}') # True
Неизменяемость в SQLAlchemy
from sqlalchemy import Column, Integer, String, create_engine, event
from sqlalchemy.orm import declarative_base, Session
from dataclasses import dataclass
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(100))
email = Column(String(100))
# Снимок (snapshot) для отслеживания изменений
@dataclass(frozen=True)
class UserSnapshot:
id: int
name: str
email: str
def entity_to_snapshot(user: User) -> UserSnapshot:
"""Преобразуем изменяемую сущность в неизменяемый снимок"""
return UserSnapshot(user.id, user.name, user.email)
# Использование
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(id=1, name='Alice', email='alice@example.com')
session.add(user)
session.commit()
# Делаем снимок
snapshot = entity_to_snapshot(user)
print(snapshot) # UserSnapshot(id=1, name='Alice', email='alice@example.com')
# Даже если пользователь изменится в БД, снимок остаётся неизменным
user.name = 'Alice Smith'
print(snapshot.name) # 'Alice' (не изменился)
Проблемы с неизменяемостью
1. Производительность памяти
# Создание много новых объектов — потребляет память
data = tuple(range(1000000))
# Каждая операция создаёт новый кортеж
new_data = data + (1000000,) # Копирует весь кортеж!
# Лучше использовать список, а потом преобразовать
data_list = list(range(1000000))
data_list.append(1000000)
final_data = tuple(data_list)
2. Сложность для вложенных структур
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Address:
street: str
city: str
@dataclass(frozen=True)
class User:
name: str
address: Address
user = User('Alice', Address('123 Main St', 'NYC'))
# Хотим изменить город — нужно создать новую структуру целиком!
new_user = User(user.name, Address(user.address.street, 'LA'))
# Лучше использовать dataclass.replace() (Python 3.10+)
new_user = replace(user, address=replace(user.address, city='LA'))
Использование с функциональным программированием
from typing import List
from dataclasses import dataclass
@dataclass(frozen=True)
class Item:
id: int
price: float
def filter_expensive(items: List[Item], min_price: float) -> List[Item]:
"""Чистая функция (без побочных эффектов)"""
return [item for item in items if item.price >= min_price]
def apply_discount(items: List[Item], discount: float) -> List[Item]:
"""Чистая функция (создаёт новые объекты)"""
return [
Item(item.id, item.price * (1 - discount))
for item in items
]
# Использование
original_items = [
Item(1, 100),
Item(2, 200),
Item(3, 150)
]
expensive = filter_expensive(original_items, 150)
discounted = apply_discount(expensive, 0.1)
print(original_items) # Не изменился
print(expensive) # Отфильтрованные товары
print(discounted) # С скидкой
Резюме: Неизменяемые структуры данных — это объекты, которые не изменяются после создания. Вместо модификации операции возвращают новые объекты. Это обеспечивает:
- Потокобезопасность (можно использовать в разных потоках без синхронизации)
- Предсказуемость (функции не имеют побочных эффектов)
- Кэшируемость (можно хешировать и использовать как ключи)
- Отслеживание истории (каждое состояние сохраняется)
Хотя неизменяемость требует больше памяти, она критична для надёжных, масштабируемых приложений.