Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I'm able to use a mutable object as a dictionary key in python. Is this not disallowed?

class A(object):
    x = 4

i = A()
d = {}

d[i] = 2

print d

i.x = 10

print d

I thought only immutable objects can be dictionary keys, but the object i above is mutable.

like image 495
Schitti Avatar asked Dec 11 '10 20:12

Schitti


People also ask

Can Python dictionary keys be mutable?

What are Keys? As shown in the figure below, keys are immutable ( which cannot be changed ) data types that can be either strings or numbers. However, a key can not be a mutable data type, for example, a list. Keys are unique within a Dictionary and can not be duplicated inside a Dictionary.

Can a dictionary be mutable?

Dictionaries themselves are mutable, so entries can be added, removed, and changed at any time. Note, though, that because entries are accessed by their key, we can't have two entries with the same key.

Can a dictionary key be an object Python?

Properties of Dictionary KeysThey can be any arbitrary Python object, either standard objects or user-defined objects. However, same is not true for the keys.

What Cannot be a key in a dictionary Python?

We can use integer, string, tuples as dictionary keys but cannot use list as a key of it .


3 Answers

Any object with a __hash__ method can be a dictionary key. For classes you write, this method defaults to returning a value based off id(self), and if equality is not determined by identity for those classes, you may be surprised by using them as keys:

>>> 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'

Changed in version 2.6: __hash__ may now be set to None to explicitly flag instances of a class as unhashable. [__hash__]

class Unhashable(object):
  __hash__ = None
like image 180
Fred Nurk Avatar answered Sep 19 '22 08:09

Fred Nurk


An object kan be a key in a dictionary if it is hashable.

Here is the definition of hashable from the documentation:

An object is hashable if it has a hash value which never changes during its lifetime (it needs a __hash__() method), and can be compared to other objects (it needs an __eq__() or__cmp__() method). Hashable objects which compare equal must have the same hash value.

Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.

All of Python’s immutable built-in objects are hashable, while no mutable containers (such as lists or dictionaries) are. Objects which are instances of user-defined classes are hashable by default; they all compare unequal, and their hash value is their id().

Since object provides a default implementation of __hash__, __eq__ and __cmp__ this means that anything deriving from object is hashable unless it is explicitly defined not to be hashable. It is not disallowed to create a mutable type that is hashable, but it might not behave as you want.

like image 35
Mark Byers Avatar answered Sep 23 '22 08:09

Mark Byers


@fred-nurk's example above luckily no longer works in Python 3, because of this change:

A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value...

Thank God for that. However, if you explicitly define __hash__() for yourself, you can still do evil things:

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's gone!
if myobject not in myset:
    print("what the hell we JUST put it in there")
like image 44
Jack O'Connor Avatar answered Sep 21 '22 08:09

Jack O'Connor