Why are tuples immutable?

Jeremy Bowers jerf at jerf.org
Thu Dec 16 06:56:50 CET 2004


On Wed, 15 Dec 2004 15:08:09 +0000, Antoon Pardon wrote:

> And I think that is a stupid reason. There are enough other situations
> were people work with mutable objects but don't wish to mutate specific
> objects.  Like objects in a sorted sequence you want to keep that way
> or objects in a heapqueue etc.
> 
> Demanding that users of dictioanaries somehow turn their mutable objects
> into tuples when used as a key and back again when you retrieve the keys
> and need the object can IMO ibe a bigger support nightmare than the
> possibility that code mutates a key in a dictionary.

Steve plonks you as a troll but I fear this is a point that either I am in
grave error on, or in our zeal to make this point simple for everyone
we've elided an important point and I'm not convinced it is as obvious as
he thinks it is. So, before trusting this message, wait for the angered
replies from those who know.

A dict key doesn't *really* need to be immutable. What it needs is the
following: the hash does not change over time, and an "equal" key will
always compare equally with no changes over time. Sample run:

>>> class MutableInteger(object):
...     def __init__(self, value):
...             self.value = value
...     def __hash__(self):
...             return hash(self.value)
...             # technically I think hash for an int just returns
...             # the int but I feel that is an implementation detail
...             # not to be depended on
...     def __eq__(self, other):
...             return self.value == other.value
...             # not robust and I know it, for demo purposes only
... 
>>> m3 = MutableInteger(33883388)
>>> m4 = MutableInteger(33883388)
>>> m3.value is m4.value
False
>>> d = {}
>>> d[m3] = "Hi!"
>>> d[m4]
'Hi!'
>>> m3.mutated = "Yes"
>>> d[m3]
'Hi!'
>>> d[m4]
'Hi!'
>>> 

In fact I use this all the time for things like matching some object I am
editing with the object representing the GUI widget; I actually give each
editable object a guaranteed unique id on creation, never changed, and I
define __eq__(self, other) as "return self is other". Then I can use a
dict with the editable objects as a key and the widget as the value, and
even when I mutate the hell out of the key, it's ok because from the
dict's point of view, the object remains immutable.

I've seen this go by over and over and ISTM nobody ever clearly says this,
so either I'm in error on some point, or people are afraid to try to
explain the properties that must hold for mutable objects to be used in
hashes. Because the slightest violation can kill you...

>>> m3.value = 55
>>> d[m3]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
KeyError: <__main__.MutableInteger object at 0xb7f5b7ec>
>>> 

(In this case, there isn't much else to violate, but a complicated
__hash__ or __eq__ function can make this arbitrarily subtle for a real
object. I actually have an "equal" method for if you want to see if two
things are *equivalent*, but you have to call it by hand, because __eq__
is consumed satisfying the properties the dict needs. In my circumstances,
this is not an issue since the only use I've ever found for that method,
though it is a huge one, is in the unit testing; in the object model I
have almost everything is run off of identity and not equality. In other
situations the choice may be more interesting.)

Demoing the need for equality:

>>> class MutableInteger2(MutableInteger):
...     def __eq__(self, other):
...             return self is other
... 
>>> d = {}
>>> m1 = MutableInteger2(333888)
>>> m2 = MutableInteger2(333888)  
>>> m1.value is m2.value
False
>>> d[m1] = "Hello!"
>>> d[m1]
'Hello!'
>>> d[m2]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
KeyError: <__main__.MutableInteger2 object at 0xb7f5bdac>
>>> 




More information about the Python-list mailing list