Comments 8
Формально нужна хэшируемость и сравнимость объектов, но по факту проще запомнить не изменяемость. Проблема с изменяемыми объектами в том, что у них при изменении меняется либо хэш, либо значение и вы уже не сможете нормально найти ключ в словаре, который ранее был задан таким объектом. Они могут и не измениться, но гарантии нет. Поэтому, от греха подальше, изменяемым объектам стараются просто не делать доступной функцию хэширования, чтобы не было возможности их использовать в качестве ключа для словаря и собирать грабли после этого.
Ну а когда вы объявляете свой класс, а не пользуетесь готовым, питон за вас заранее не знает, что у вас там будет с изменяемостью, поэтому вы сами должны проследить за тем, будет ли у вашего класса функция хэширования и если да, то вы должны сделать её адекватной и сообразной функции сравнения.
Вы относите слово "неизменяемый" к содержимому структуры данных, но применительно к ключам ассоциативного массива (в Python - "словаря") смотреть надо не на неизменяемость содержимого, а на неизменяемость того, что хэшируется и/или сравнивается операциями == и !=.
В случае объектов хэшируется/сравнивается не содержимое объектов, а никак не изменяемые указатели (адрес в памяти, id в таблице объектов и т.п.) этих объектов. Независимо от того, как меняются значения полей объекта, указатель и хэш, полученный из этого указателя, остаются неизменными. Аналогично с функциями.
"зачастую на интернет-ресурсах про ключи говорят как про объекты неизменяемых типов данных,"
Не знаю где так говорят, если читать книги по Rust и С++ там сказано если ты можешь захешировать свой тип - вперёд
dct = {obj_1: 1, obj_2: 2}
Хм, интересная конструкция: мы присваивает каждому объекту значение, например, рейтинг, при этом не внутри объекта, т.е. ни объект, ни класс об этом "не знают". Где бы это могло пригодиться? [Дай 10 вариантов] - строка, отделяющая теперешнее время от времени ещё несколько лет назад...
Ответ deepseek, который ещё несколько лет назад был бы немыслимо ждать от машины:
Отличный вопрос! Хранение внешних атрибутов объектов в словаре — мощный паттерн, особенно когда нельзя или нежелательно модифицировать сами классы объектов. Вот 10 практических применений:
Кэширование результатов вычислений:
cache = {obj: expensive_calculation(obj) for obj in objects}
Пригодится: Для тяжёлых функций (рендеринг, сложные математические операции). Избегаем повторных вычислений для одного объекта.
Временные состояния/флаги:
selected_objects = {obj_1: True, obj_2: False}
Пригодится: UI (выделение элементов), обработка событий ("уже обработан"), временные отметки в алгоритмах (обход графа -
visited[node] = True
).
Связывание метаданных:
object_metadata = {obj: {"last_accessed": timestamp, "owner": user_id}}
Пригодится: Системы кеширования (время доступа), аудит (кто последний менял), контекстная информация для обработки.
Подсчёт частоты / Агрегация:
word_count = {}; for word in words: word_count[word] = word_count.get(word, 0) + 1
Пригодится: Анализ текста (частотность слов), подсчёт событий (сколько раз пользователь выполнил действие), поиск дубликатов.
Сопоставление объектов с внешними ресурсами:
texture_mapping = {game_object: texture_id}
Пригодится: Графика (связь модели с текстурой), базы данных (связь объекта ORM с ID записи), сетевые соединения (связь пользователя с сокетом).
Приоритизация / Очереди:
priority_queue = []; heapq.heappush(priority_queue, (priority_dict[task], task))
Пригодится: Планировщики задач (приоритет задачи не свойство самой задачи, а определяется контекстом), алгоритмы поиска пути (A* -
f_score[node] = ...
).
Вес рёбер / Сила связей (Графы):
graph[from_node] = {to_node: weight, ...}
Пригодится: Представление взвешенных графов (социальные сети - сила связи, карты - расстояние/время проезда). Вес - атрибут связи, а не узла.
Внешняя конфигурация / Настройки:
object_settings = {widget: {"color": "red", "size": 10}}
Пригодится: GUI (стилизация элементов, которые не хранят стиль внутри), управление поведением плагинов. Настройки задаются извне.
Группировка / Классификация:
groups = {"admin": [user1, user2], "user": [user3]}
Пригодится: Системы прав доступа (группы пользователей), категоризация элементов (товары в категориях). Группа - внешний контейнер.
Хранение сессионных данных:
session_store = {session_id: {"user": user_obj, "cart": cart_items}}
Пригодится: Веб-приложения. Данные сессии (корзина покупок, состояние) привязаны к идентификатору сессии, а не к объекту пользователя напрямую (особенно если пользователь не залогинен).
Ключевые преимущества такого подхода:
Неинвазивность: Не нужно изменять исходные классы объектов.
Гибкость: Легко добавлять, изменять и удалять атрибуты для объектов динамически.
Разделение ответственности: Данные, специфичные для контекста использования, хранятся отдельно от базовых данных объекта.
Эффективность: Быстрый (O(1) в среднем) доступ к данным по ключу-объекту.
Работа с "чужими" объектами: Можно прикреплять данные к объектам из библиотек, код которых вы не контролируете.
Важные замечания:
Хешируемость: Ключи словаря (ваши объекты) должны быть хешируемыми (в Python обычно реализуют
hash
иeq
).Сравнение по ссылке: По умолчанию в Python объекты сравниваются по идентификатору (адресу в памяти). Это часто ожидаемо в таких сценариях.
Жизненный цикл: Словарь хранит сильные ссылки на объекты-ключи. Это может препятствовать сборке мусора, если объект больше нигде не используется, но остался в словаре. Для временных данных используйте
weakref.WeakKeyDictionary
.
Этот паттерн — отличный инструмент для случаев, когда данные контекстно-зависимы или временны, и их не стоит "зашивать" в саму сущность объекта.
Хз откуда здесь "чудеса" и "открытия". Это буквально база работы со словарями о которой написано в любой нормальной книге по питону. Или любом нормальном курсе. Да и статей где бы все детально разжёвывалось (real python тот же) предостаточно...
О сколько нам открытий чудных готовит Python