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?
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.
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.)
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.
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))