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

Когда вычисляется хэш-объект python и почему хэш -1-го другого?

Следуя из this вопроса, мне интересно узнать, когда вычисляется хэш-объект python?

  • В экземпляре __init__ время,
  • В первый раз вызывается __hash__(),
  • Каждый раз, когда вызывается __hash__(), или
  • Любая другая возможность, которую я могу пропустить?

Может ли это отличаться в зависимости от типа объекта?

Почему hash(-1) == -2, в то время как другие целые числа равны их хешу?

4b9b3361

Ответ 1

Хэш обычно вычисляется каждый раз, когда он используется, так как вы можете легко проверить себя (см. ниже). Конечно, любой конкретный объект может кэшировать свой хэш. Например, строки CPython делают это, но кортежи не работают (см., Например, этот отклоненный отчет об ошибке по причинам).

Хэш-значение -1 сигнализирует об ошибке для CPython. Это связано с тем, что C не имеет исключений, поэтому ему нужно использовать возвращаемое значение. Когда объект Python __hash__ возвращает -1, CPython фактически безмолвно изменит его на -2.

Смотрите сами:

class HashTest(object):
    def __hash__(self):
        print('Yes! __hash__ was called!')
        return -1

hash_test = HashTest()

# All of these will print out 'Yes! __hash__ was called!':

print('__hash__ call #1')
hash_test.__hash__()

print('__hash__ call #2')
hash_test.__hash__()

print('hash call #1')
hash(hash_test)

print('hash call #2')
hash(hash_test)

print('Dict creation')
dct = {hash_test: 0}

print('Dict get')
dct[hash_test]

print('Dict set')
dct[hash_test] = 0

print('__hash__ return value:')
print(hash_test.__hash__())  # prints -1
print('Actual hash value:')
print(hash(hash_test))  # prints -2

Ответ 2

Из здесь:

Значение хэша -1 зарезервировано (оно используется для флага ошибок в реализации C). Если хэш-алгоритм генерирует это значение, вместо этого мы просто используем -2.

Поскольку целочисленный хеш является целым числом, он сразу изменился.

Ответ 3

Легко видеть, что для объектов, определенных пользователем, используется опция # 3. Это позволяет хешу изменять, если вы мутируете объект, но если вы когда-либо используете этот объект в качестве словарного ключа, вы должны быть уверены, что хеш не изменится.

>>> class C:
    def __hash__(self):
        print("__hash__ called")
        return id(self)


>>> inst = C()
>>> hash(inst)
__hash__ called
43795408
>>> hash(inst)
__hash__ called
43795408
>>> d = { inst: 42 }
__hash__ called
>>> d[inst]
__hash__ called

Строки используют параметр # 2: они вычисляют значение хэша один раз и кэшируют результат. Это безопасно, потому что строки неизменяемы, поэтому хэш никогда не может измениться, но если вы подклассом str, результат не может быть неизменным, поэтому метод __hash__ будет вызываться каждый раз снова. Кортежи обычно считаются неизменяемыми, поэтому вы можете думать, что хэш может быть кэширован, но на самом деле хеш кортежа зависит от хеша его содержимого и может включать изменяемые значения.

Для @max, который не считает, что подклассы str могут изменять хэш:

>>> class C(str):
    def __init__(self, s):
        self._n = 1
    def __hash__(self):
        return str.__hash__(self) + self._n


>>> x = C('hello')
>>> hash(x)
-717693723
>>> x._n = 2
>>> hash(x)
-717693722