Недавно мы с подписчиками моего ТГ-канала спорили по поводу, казалось бы, совсем простого вопроса – элементы каких типов данных могут быть ключами словаря. Во многих статьях в сети написано, что это объект любого неизменяемого типа данных, например, числа, строки, кортежа. Т.е. ключ словаря – всегда объект неизменяемого типа данных. Но так ли это? А что насчет ключей в виде объектов пользовательских именных функций или экземпляров пользовательских классов. Конечно, мы вряд ли будем создавать такие ключи, но все же их можно использовать или нет?
Проверим:
class MyClass: pass obj_1 = MyClass() obj_2 = MyClass() dct = {obj_1: 1, obj_2: 2} print(dct[obj_1]) def func(): pass dct = {func: 1} print(dct[func])
Ошибок нет. Получается, что может экземпляр класса и объект пользовательской функции быть ключом! Но ведь это изменяемые типы данных. И это несложно проверить. Если объект относится к изменяемому типу данных, то при попытке изменения id объекта должен остаться прежним.
Вот числа, они неизменяемые:
x = 10 y = x print(id(x), id(y)) x = x + 1 print(id(x), id(y))
Результат:

При «изменении» значения переменной x мы получили совсем новый объект.
Теперь то же самое повторим на списках:
x = [1, 2, 3] y = x print(id(x), id(y)) x.append(4) print(id(x), id(y))
Результат:

Идентификатор не изменился, т.е. имеем объект изменяемого типа данных.
Теперь посмотрим, что с изменяемостью у экземпляров пользовательских классов:
class User: def __init__(self, age): self.age = age u = User(12) b = u print(id(u), id(b)) u.age = 40 print(id(u), id(b)) print(b.age)
Результат:

Получается экземпляры пользовательских классов – изменяемые.
А что с объектами функций? Можем мы у объекта существующей функции изменить логику программным путем? Пример ниже показывает, что можем, т.к. id объекта функции не изменился:
def func(): return "hello" print(id(func)) def other(a, b): return a + b func.__code__ = other.__code__ print(id(func))
Результат:

И это все правильно. Объекты изменяемых типов данных при изменениях сохраняют свой id. Об этом указано в документации.
Значит изменяемость здесь совсем не при чем? В чем же тогда дело и как понять объекты каких классов могут быть ключами словаря? Обратимся к теории.
Словарь, это хеш-таблица, т.е. ключи должны быть объектами классов, у которых существует метод hash. В Python пользовательские функции и классы по умолчанию хешируются, а значит в качестве ключей использоваться могут, но при этом они изменяемые.
P.S. Теперь нужно понять, почему зачастую на интернет-ресурсах про ключи говорят как про объекты неизменяемых типов данных, хотя нужно делать упор не на изменяемость, а на хешируемость. А что вы думаете об этом?
