Подтвердить что ты не робот

Почему и как работают функции Python?

Недавно я попробовал следующие команды в Python:

>>> {lambda x: 1: 'a'}
{<function __main__.<lambda>>: 'a'}

>>> def p(x): return 1
>>> {p: 'a'}
{<function __main__.p>: 'a'}

Успех обоих творений dict указывает на то, что как лямбда, так и регулярные функции хешируются. (Нечто вроде {[]: 'a'} выходит из строя с TypeError: unhashable type: 'list').

Хэш, по-видимому, не обязательно является идентификатором функции:

>>> m = lambda x: 1
>>> id(m)
140643045241584
>>> hash(m)
8790190327599
>>> m.__hash__()
8790190327599

Последняя команда показывает, что метод __hash__ явно определен для lambda s, т.е. это не какая-то автоматическая вещь, которую Python вычисляет на основе типа.

Какова мотивация создания функций hashable? Для бонуса, каков хэш функции?

4b9b3361

Ответ 1

Это ничего особенного. Как вы можете видеть, рассматриваете ли вы метод unbound __hash__ для типа функции:

>>> def f(): pass
...
>>> type(f).__hash__
<slot wrapper '__hash__' of 'object' objects>

часть of 'object' objects означает, что она наследует только __hash__ на основе идентификатора от object. Функция == и hash работает по идентичности. Разница между id и hash является нормальной для любого типа, наследующего object.__hash__:

>>> x = object()
>>> id(x)
40145072L
>>> hash(x)
2509067

Вы могли бы подумать, что __hash__ должен быть определен только для неизменяемых объектов, и вы почти правы, но это не содержит ключевой детали. __hash__ должен быть определен только для объектов, где все, что связано с сравнениями ==, неизменно. Для объектов, чей == основан на идентичности, он полностью стандартен для базы hash и для идентичности, так как даже если объекты являются изменяемыми, они не могут быть изменчивыми способом, который изменил бы их личность. Файлы, модули и другие изменяемые объекты с использованием == на основе идентификаторов ведут себя так.

Ответ 2

Может быть полезно, например, создавать наборы объектов функций или индексировать функции dict. Неизменяемые объекты обычно поддерживают __hash__. В любом случае нет внутренней разницы между функцией, определяемой def или lambda, которая является чисто синтаксической.

Используемый алгоритм зависит от версии Python. Похоже, вы используете последнюю версию Python в 64-битной коробке. В этом случае хэш функционального объекта является правильным вращением его id() на 4 бита, причем результат рассматривается как подписанное 64-битное целое число. Правый сдвиг выполняется, потому что адреса объектов (id() results) обычно выравниваются так, что их последние 3 или 4 бита всегда равны 0, а это мягко раздражающее свойство для хэш-функции.

В вашем конкретном примере

>>> i = 140643045241584 # your id() result
>>> (i >> 4) | ((i << 60) & 0xffffffffffffffff) # rotate right 4 bits
8790190327599  # == your hash() result

Ответ 3

Функция хешируется, потому что это обычный, встроенный, не изменяемый объект.

Из Руководство по Python:

Объект hashable, если он имеет значение хэша, которое никогда не изменяется в течение его жизненного цикла (ему нужен метод __hash__()), и его можно сравнить с другими объектами (ему нужен метод __eq__() или __cmp__()). Объекты Hashable, которые сравниваются равными, должны иметь одно и то же значение хэш-функции.

Hashability позволяет использовать объект как ключ словаря и член набора, поскольку эти структуры данных используют внутреннее значение хэша.

Все неиспользуемые встроенные объекты Pythons являются хешируемыми, в то время как нет изменяемых контейнеров (таких как списки или словари). Объекты, являющиеся экземплярами пользовательских классов, по умолчанию хешируются; все они сравниваются неравномерно (кроме самих себя), и их хэш-значение получается из их id().