Я могу использовать изменяемый объект в качестве словарного ключа в python. Разве это не запрещено?

class A(object):
    x = 4

i = A()
d = {}

d[i] = 2

print d

i.x = 10

print d

Я думал, что только неизменяемые объекты могут быть словарными клавишами, но объект я выше изменен.

Ответы

Ответ 1

Любой объект с __hash__ может быть ключом словаря. Для классов, которые вы пишете, этот метод по умолчанию возвращает значение, основанное на id (self), и если равенство не определяется идентификатором для этих классов, вы можете быть удивлены, используя их как ключи:

>>> class A(object):
...   def __eq__(self, other):
...     return True
... 
>>> one, two = A(), A()
>>> d = {one: "one"}
>>> one == two
True
>>> d[one]
'one'
>>> d[two]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: <__main__.A object at 0xb718836c>

>>> hash(set())  # sets cannot be dict keys
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

Изменено в версии 2.6: __hash__ теперь может быть установлено в None, чтобы явно указывать экземпляры класса как неотображаемые. [__hash__]

class Unhashable(object):
  __hash__ = None

Ответ 2

Требование состоит в том, что хэш объекта не изменяется со временем и что он сравнивает равный (==) с его исходным значением. Ваш класс A отвечает этим требованиям, поэтому он делает действительный ключ словаря. Атрибут x вообще не учитывается при манипуляции ключами, только идентификатор объекта.

Ответ 3

Объект kan является ключом в словаре, если он hashable.

Вот определение хеширования из документации:

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

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

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

Так как object обеспечивает реализацию по умолчанию __hash__, __eq__ и __cmp__, это означает, что все, происходящее из object, является хешируемым, если оно явно не определено как хешируемое. Не разрешается создавать изменяемый тип, который можно использовать для хэширования, но он может не вести себя так, как вы хотите.

Ответ 4

Пример

@fred-nurk выше, к счастью, больше не работает в Python 3, из-за это изменение:

Класс, который переопределяет __eq__() и не определяет __hash__(), будет иметь __hash__() неявно установленный в None. Когда метод __hash__() класса None, экземпляры класса поднимут соответствующий TypeError, когда программа попытается получить их хеш-значение...

Слава Богу за это. Однако, если вы явно определяете __hash__() для себя, вы все равно можете делать злые вещи:

class BadHasher:
    def __init__(self):
        self.first = True

    # Implement __hash__ in an evil way. The first time an instance is hashed,
    # return 1. Every time after that, return 0.
    def __hash__(self):
        if self.first:
            self.first = False
            return 1
        return 0

myobject = BadHasher()
# We can put this object in a set...
myset = {myobject}
# ...but as soon as we look for it, it gone!
if myobject not in myset:
    print("what the hell we JUST put it in there")