Python: why can I put mutable object in a dict or

2019-02-17 11:21发布

问题:

Given the following example,

class A(object):
    pass
a = A()
a.x = 1

Obviously a is mutable, and then I put a in a set,

set([a])

It succeeded. Why I can put mutable object like "a" into a set/dict? Shouldn't set/dict only allow immutable objects so they can identify the object and avoid duplication?

回答1:

Python doesn't test for mutable objects, it tests for hashable objects.

Custom class instances are by default hashable. That's fine because the default __eq__ implementation for such classes only tests for instance identity and the hash is based of the same information.

In other words, it doesn't matter that you alter the state of your instance attributes, because the identity of an instance is immutable anyway.

As soon as you implement a __hash__ and __eq__ method that take instance state into account you might be in trouble and should stop mutating that state. Only then would a custom class instance no longer be suitable for storing in a dictionary or set.



回答2:

From the docs the requirement are that it must be hashable and can be compared:

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 (except with themselves), and their hash value is their id().

You can see from the last part that user defined classes (emphasis is mine) are hashable by default

There is no mention in the docs about mutability requirements for set:

class set([iterable]) class frozenset([iterable]) Return a new set or frozenset object whose elements are taken from iterable. The elements of a set must be hashable. To represent sets of sets, the inner sets must be frozenset objects. If iterable is not specified, a new empty set is returned.)

For a dict again the requirement is that the key is hashable:

A mapping object maps hashable values to arbitrary objects. Mappings are mutable objects. There is currently only one standard mapping type, the dictionary. (For other containers see the built in list, set, and tuple classes, and the collections module.)



回答3:

After doing some more research, I was able to find out the reason why I would think set and dict only allow immutable objects as the entries and keys respectively, which is incorrect. I think it's important for me to clarify that here as I'm sure there are people who are new to Python having the same doubt as I had before. There are two reasons for me to previously confuse immutability with hashability. First reason is that all the built-in immutable objects (tuple, frozenset...) are allowed as set entries or dict keys, while all of the built-in mutable container objects are not. Second reason is actually where this question was derived from. I was reading the book Mastering Object-Oriented Python. In the part it explains the __hash__ function, it leads the reader into thinking immutability is the prerequisite of hashability.

The definition of immutability is for an object, after its creation, you can't change the value of its existing attributes or creating new ones. So it has nothing to do with hashability, which requires an object to have their __hash__() and __eq__() methods defined, and their hash value to be immutable during their lifecycle. Actually hashable objects are immutable in terms of their hash value. I guess that's one of the reason these two concepts get confused so often.



回答4:

the id does not change when you add members. so there is no reason for that not to work.

class A(object):
    pass
a = A()
print(id(a))

a.x = 1
print(id(a))