Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# frozenset: Неизменяемое множество
frozenset — это неизменяемая версия set. Это значит, что после создания frozenset не может быть изменен. Это мощный инструмент для определенных случаев.
Основная разница: set vs frozenset
# set — изменяемый
s = {1, 2, 3}
s.add(4) # Работает
s.remove(1) # Работает
s[0] # TypeError — нет индексирования
# frozenset — неизменяемый
fs = frozenset([1, 2, 3])
fs.add(4) # AttributeError — нет метода add
fs.remove(1) # AttributeError — нет метода remove
fs[0] # TypeError — нет индексирования
# Зато frozenset hashable
hash(fs) # Работает: 8765432101
hash(s) # TypeError: unhashable type: 'set'
Главные причины использовать frozenset
1. Использование как ключа в dict (САМАЯ ЧАСТАЯ ПРИЧИНА)
Поскольку frozenset hashable, его можно использовать как ключ в словаре или элемент другого set.
# Плохо — set не может быть ключом
data = {}
key = {1, 2, 3}
data[key] = "value" # TypeError: unhashable type: 'set'
# Хорошо — frozenset может быть ключом
data = {}
key = frozenset([1, 2, 3])
data[key] = "value" # Работает!
print(data[key]) # "value"
Практический пример: Кеширование комбинаций
def calculate_result(parameters):
"""Вычисляет результат для набора параметров."""
cache = {}
# Параметры — это кортежи (хеш-устойчивые)
# Но если параметры более сложные, нужен frozenset
# Приходит набор настроек: {"debug": True, "mode": "prod", ...}
# Хочу кешировать по всем этим настройкам
# Вариант 1: Плохо — convert to string
key = str(sorted(parameters.items())) # Может быть не уникально
# Вариант 2: Хорошо — используем frozenset
key = frozenset(parameters.items())
if key in cache:
return cache[key]
result = do_expensive_calculation(parameters)
cache[key] = result
return result
2. Элемент другого set
# Плохо
sets_of_sets = {
{1, 2, 3}, # TypeError: unhashable type: 'set'
{4, 5, 6}
}
# Хорошо
sets_of_sets = {
frozenset([1, 2, 3]),
frozenset([4, 5, 6]),
frozenset([1, 2, 3]) # Дублер будет удален
}
print(len(sets_of_sets)) # 2 (дублер удален благодаря set-у)
# Используем для удаления дублей списков списков
lists = [
[1, 2, 3],
[4, 5, 6],
[1, 2, 3] # Дублер
]
unique_lists = [list(fs) for fs in {frozenset(l) for l in lists}]
print(unique_lists) # [[1, 2, 3], [4, 5, 6]]
3. Правильная работа с множественными наборами
# Часто нужно найти, какие наборы параметров мы уже обработали
processed_configs = set()
# Плохо: кортежи могут быть медленнее для больших данных
config1 = ("debug", "true", "env", "prod", "feature_flags", "v2")
processed_configs.add(config1)
# Хорошо: frozenset для произвольного порядка
config1 = frozenset({("debug", "true"), ("env", "prod"), ("feature_flags", "v2")})
processed_configs.add(config1)
# Плюс: порядок не важен
config2 = frozenset({("feature_flags", "v2"), ("env", "prod"), ("debug", "true")})
print(config1 == config2) # True!
print(config1 in processed_configs) # True
4. Constancy в API (информирование о неизменяемости)
def get_allowed_colors() -> frozenset:
"""Возвращает неизменяемый набор допустимых цветов."""
return frozenset(["red", "green", "blue"])
# Потребитель функции видит в типе, что это неизменяемо
colors = get_allowed_colors()
colors.add("yellow") # AttributeError — явный сигнал
# vs
def get_allowed_colors() -> set:
"""Возвращает набор допустимых цветов."""
return {"red", "green", "blue"}
# Потребитель может по ошибке изменить
colors = get_allowed_colors()
colors.add("yellow") # Работает, но может сломать логику
Практические примеры
Пример 1: Поиск пересечений и удаление дублей
def find_duplicate_lists(lists: list[list]) -> list[list]:
"""Находит дублирующиеся списки."""
seen = set()
duplicates = []
for lst in lists:
fs = frozenset(lst) # Преобразуем в frozenset для hashability
if fs in seen:
duplicates.append(lst)
seen.add(fs)
return duplicates
lists = [
[1, 2, 3],
[4, 5, 6],
[2, 1, 3], # Дублер (порядок не важен)
[1, 2, 3], # Дублер
]
print(find_duplicate_lists(lists)) # [[2, 1, 3], [1, 2, 3]]
Пример 2: Граф и поиск циклов
class Graph:
def __init__(self):
self.edges = set()
def add_edge(self, u, v):
"""Добавляет неориентированное ребро (порядок не важен)."""
edge = frozenset([u, v]) # {u, v} == {v, u}
self.edges.add(edge)
def has_edge(self, u, v):
"""Проверяет наличие ребра."""
edge = frozenset([u, v])
return edge in self.edges
g = Graph()
g.add_edge(1, 2)
g.add_edge(2, 3)
print(g.has_edge(1, 2)) # True
print(g.has_edge(2, 1)) # True (порядок не важен!)
print(len(g.edges)) # 2
Пример 3: Кеширование функций с множественными параметрами
from functools import lru_cache
# Проблема: @lru_cache не работает с unhashable типами
@lru_cache(maxsize=128)
def process_items(items: list):
# TypeError: unhashable type: 'list'
return sum(items)
# Решение: используем frozenset
from functools import lru_cache
@lru_cache(maxsize=128)
def process_items(items: frozenset):
return sum(items) # Работает!
result1 = process_items(frozenset([1, 2, 3]))
result2 = process_items(frozenset([3, 2, 1])) # Кеш попадает!
Пример 4: Атомарность операций в многопоточности
import threading
class PermissionManager:
def __init__(self):
self.user_permissions = {} # user_id -> frozenset permissons
self.lock = threading.Lock()
def set_permissions(self, user_id, permissions: list):
"""Устанавливает permissions (неизменяемо)."""
with self.lock:
self.user_permissions[user_id] = frozenset(permissions)
def get_permissions(self, user_id) -> frozenset:
"""Получает permissions (не может быть изменено снаружи)."""
with self.lock:
return self.user_permissions.get(user_id, frozenset())
def has_permission(self, user_id, permission: str) -> bool:
perms = self.get_permissions(user_id)
return permission in perms
manager = PermissionManager()
manager.set_permissions(1, ["read", "write"])
# Потребитель получает frozenset
perms = manager.get_permissions(1)
perms.add("delete") # AttributeError — не может изменить!
# Это гарантирует, что permissions остаются корректными
Пример 5: Валидация множественного выбора
class PizzaOrder:
ALLOWED_TOPPINGS = frozenset(["pepperoni", "mushrooms", "onions", "sausage"])
def __init__(self, toppings: list):
selected = frozenset(toppings)
# Проверяем, что все выбранные топпинги допустимы
if not selected.issubset(self.ALLOWED_TOPPINGS):
invalid = selected - self.ALLOWED_TOPPINGS
raise ValueError(f"Invalid toppings: {invalid}")
self.toppings = selected
def get_excluded_toppings(self):
"""Возвращает топпинги, которые НЕ выбраны."""
return self.ALLOWED_TOPPINGS - self.toppings
order = PizzaOrder(["pepperoni", "mushrooms"])
print(order.get_excluded_toppings()) # frozenset({'onions', 'sausage'})
Производительность
import timeit
# set
t_set = timeit.timeit(lambda: {1, 2, 3, 4, 5} in [{1, 2, 3, 4, 5}], number=100000)
# TypeError — set не может быть в list, требует convert
# frozenset
t_frozenset = timeit.timeit(
lambda: frozenset([1, 2, 3, 4, 5]) in [frozenset([1, 2, 3, 4, 5])],
number=100000
)
print(f"frozenset: {t_frozenset:.4f}s")
# frozenset в dict
d = {frozenset([1, 2, 3]): "value"}
t_dict_lookup = timeit.timeit(
lambda: d[frozenset([1, 2, 3])],
number=100000
)
print(f"dict lookup: {t_dict_lookup:.4f}s")
Когда НЕ использовать frozenset
# Плохо: если нужны быстро менять данные
config = frozenset({"debug": True})
config.add("prod": False) # AttributeError
# Нужно переделать:
config = frozenset({("debug", True), ("prod", False)})
# Плохо: если нужно сохранять порядок
fs = frozenset([3, 1, 2])
# frozenset не гарантирует порядок
# Используй tuple вместо этого
# Плохо: если нужна большая изменяемость
# frozenset лучше всего для "напиши один раз, читай много раз"
Ключевые выводы
- frozenset — неизменяемый set, может быть ключом в dict
- Главное применение: Использование как ключа в dict или элемента другого set
- Кеширование: Когда нужно кешировать по множеству параметров
- API контракт: frozenset явно показывает, что данные неизменяемы
- Производительность: O(1) для проверки принадлежности (как set)
- Используй когда:
- Нужен hashable набор
- Хочешь гарантировать immutability
- Нужно дедублировать списки списков
- Кешируешь по множественным параметрам
- Не используй когда:
- Нужна частая модификация
- Важен порядок элементов