Какие знаешь иммутабельные структуры данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Иммутабельные структуры данных в Python
Иммутабельные (неизменяемые) типы данных — это структуры, которые после создания нельзя изменить. Это критически важно для безопасности потоков, кеширования и использования в качестве ключей. Разберу все встроенные и популярные варианты.
1. Встроенные иммутабельные типы
str (строка) — иммутабельна
s = "hello"
# s[0] = "H" # TypeError: 'str' object does not support item assignment
# Если кажется, что меняешь — на самом деле создаёшь новую
s2 = s.upper() # Создана новая строка
print(s) # "hello" (исходная не изменилась)
print(s2) # "HELLO"
# Объединение тоже создаёт новую строку
s3 = s + "!"
int, float, bool — иммутабельны
x = 42
y = x
y = 43 # Создан новый объект, x не изменился
print(x) # 42
# int кешируются (-5 до 256)
a = 256
b = 256
print(a is b) # True (один и тот же объект)
c = 257
d = 257
print(c is d) # False (разные объекты)
None — иммутабельна
a = None
b = None
print(a is b) # True
2. tuple (кортеж) — иммутабельна
Список, но неизменяемый.
t = (1, 2, 3)
# t[0] = 10 # TypeError: 'tuple' object does not support item assignment
# t.append(4) # AttributeError: 'tuple' object has no attribute 'append'
# Но элементы могут быть мутабельными!
t = ([1, 2], [3, 4])
t[0].append(999) # МОЖЕШЬ изменить список внутри!
print(t) # ([1, 2, 999], [3, 4])
# Используешь для распаковки
a, b, c = (10, 20, 30)
print(a, b, c) # 10 20 30
# Возврат нескольких значений
def get_user():
return ("John", 30, "john@example.com")
name, age, email = get_user()
Производительность:
# Кортежи быстрее списков
import timeit
list_time = timeit.timeit(lambda: [1, 2, 3, 4, 5])
tuple_time = timeit.timeit(lambda: (1, 2, 3, 4, 5))
print(f"List: {list_time}, Tuple: {tuple_time}")
# Tuple быстрее примерно на 30%
3. frozenset — иммутабельное множество
Множество, но неизменяемое. Можешь использовать в качестве ключа.
# Обычное множество (мутабельное)
s = {1, 2, 3}
s.add(4) # Можешь изменять
# Frozenset (иммутабельное)
fs = frozenset([1, 2, 3])
# fs.add(4) # AttributeError: 'frozenset' object has no attribute 'add'
# Можешь использовать как ключ
data = {fs: "value"} # OK
data[s] = "value" # TypeError: unhashable type: 'set'
# Операции множества работают
fs1 = frozenset([1, 2, 3])
fs2 = frozenset([2, 3, 4])
print(fs1 & fs2) # frozenset({2, 3}) пересечение
print(fs1 | fs2) # frozenset({1, 2, 3, 4}) объединение
4. Иммутабельные типы из collections
namedtuple — именованный кортеж
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x) # 10 (доступ как атрибут)
print(p[0]) # 10 (доступ как индекс)
# Всё ещё иммутабельна
# p.x = 15 # AttributeError: can't set attribute
# Неплохо для возврата нескольких значений
UserInfo = namedtuple('UserInfo', ['name', 'age', 'email'])
user = UserInfo("Alice", 30, "alice@example.com")
ChainMap — вид на несколько словарей (но не иммутабельна)
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
chain = ChainMap(dict1, dict2)
print(chain['a']) # 1 (из первого)
print(chain['b']) # 2 (из первого, приоритет)
print(chain['c']) # 4 (из второго)
5. Типы из types
MappingProxyType — read-only вид на словарь
from types import MappingProxyType
original = {'a': 1, 'b': 2}
read_only = MappingProxyType(original)
print(read_only['a']) # 1
# read_only['a'] = 10 # TypeError: 'mappingproxy' object does not support item assignment
# Но если меняешь original, меняется и view
original['a'] = 999
print(read_only['a']) # 999
6. Иммутабельные структуры из сторонних библиотек
pyrsistent — функциональные иммутабельные структуры
from pyrsistent import v, m, s
# Иммутабельный список (PVector)
v1 = v(1, 2, 3)
v2 = v1.append(4) # Возвращает новый вектор
print(v1) # v(1, 2, 3) (исходный не изменился)
print(v2) # v(1, 2, 3, 4)
# Иммутабельный словарь (PMap)
m1 = m(a=1, b=2)
m2 = m1.set('c', 3) # Возвращает новый словарь
print(m1) # pmap({'a': 1, 'b': 2})
print(m2) # pmap({'a': 1, 'b': 2, 'c': 3})
# Иммутабельное множество (PSet)
s1 = s(1, 2, 3)
s2 = s1.add(4) # Возвращает новое множество
print(s1) # pset([1, 2, 3])
print(s2) # pset([1, 2, 3, 4])
attrs с frozen=True
from attrs import define
@define(frozen=True)
class User:
name: str
age: int
email: str
user = User("Alice", 30, "alice@example.com")
# user.age = 31 # attrs.exceptions.FrozenInstanceError
# Но можешь создать новый
user2 = attrs.evolve(user, age=31)
print(user.age) # 30 (исходный не изменился)
print(user2.age) # 31
dataclasses с frozen=True (Python 3.7+)
from dataclasses import dataclass
@dataclass(frozen=True)
class Product:
name: str
price: float
stock: int
product = Product("Laptop", 999.99, 10)
# product.price = 899.99 # dataclasses.FrozenInstanceError
7. Кортежи с вложенными мутабельными объектами
Опасность: кортеж иммутабельна, но содержимое может меняться
data = ([1, 2, 3], {"key": "value"})
# Кортеж сам по себе иммутабельный
# data[0] = [4, 5, 6] # TypeError
# Но содержимое можешь менять
data[0].append(4) # OK
data[1]["new_key"] = "new_value" # OK
print(data) # ([1, 2, 3, 4], {'key': 'value', 'new_key': 'new_value'})
# Если нужна полная иммутабельность — используй вложенные иммутабельные типы
data = ((1, 2, 3), frozenset(['key', 'value'])) # Полностью иммутабельна
8. Создание собственных иммутабельных классов
from dataclasses import dataclass
@dataclass(frozen=True)
class Color:
r: int
g: int
b: int
def __post_init__(self):
# Валидация
if not all(0 <= c <= 255 for c in (self.r, self.g, self.b)):
raise ValueError("RGB values must be 0-255")
@property
def hex(self):
return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
# Использование
red = Color(255, 0, 0)
print(red.hex) # #ff0000
# Как ключ в словаре
colors = {red: "Red", Color(0, 255, 0): "Green"}
Сравнение иммутабельных типов
Тип | Иммутабельна | Hashable | Используем в | Производительность
-----------------|--------------|----------|------------------|----------------
str | Да | Да | Ключи, sets | Быстрая
int, float, bool | Да | Да | Ключи, sets | Очень быстрая
tuple | Да* | Да | Ключи, sets | Быстрая
frozenset | Да | Да | Ключи, sets | Средняя
namedtuple | Да | Да | Данные, ключи | Быстрая
list | Нет | Нет | Коллекции | Быстрая
dict | Нет | Нет | Коллекции | Быстрая
set | Нет | Нет | Коллекции | Быстрая
* Если содержит мутабельные элементы — не полностью иммутабельна
Когда использовать иммутабельные типы
- Безопасность потоков (threading)
# Безопасно в многопоточности
shared_data = (1, 2, 3) # Не нужны locks
- Ключи словарей
cache = {("user", 123): "John"} # Можешь
data = {["user", 123]: "John"} # Нельзя
- Хеширование и кеширование
hashed_config = hash(("debug", True, 30)) # OK
- Функциональное программирование
from functools import reduce
def process(data):
# Функция не меняет входные данные
return (data[0] * 2, data[1] + 10) # Возвращает новый кортеж
- Явное намерение: это не должно меняться
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
# Сигнал: конфиг не должен меняться
debug: bool
timeout: int