lambda

Nick Coghlan ncoghlan at iinet.net.au
Wed Jan 19 08:14:03 EST 2005


Antoon Pardon wrote:
	> I can be wrong, but until now I have seen no indication that I was
> using mutable and immutable differently than other people. AFAICT
> we all refer to whether an object belongs to a mutable or immutable
> class.

The difference is that when you take a copy of the key and stick it in the 
dictionary, such that the dictionary now holds the *sole* reference to that key, 
you have made that key *effectively* immutable.

This is why no-one really batted an eyelid when you mentioned that mutable keys 
can be used safely by making a copy - doing so makes the key *effectively* 
immutable, and means that modifying the original key object (i.e. the 
application's copy, not the dict's copy), and reusing it to look up in the 
dictionary is likely to give a KeyError.

These semantics would be understandably surprising to many users, and hence, are 
not supplied by default.

Additionally, a dictionary's keys are accessible via its API. Accordingly, to 
preserve this 'effective immutability', making a copy on key input is 
insufficient - keys must be copied on *output* as well (that is, dict.keys, 
dict.items etc must return *copies* of the key objects, not the key objects 
themselves).

Since there is no reliable way in Python to tell if an object is mutable or not 
(the closest equivalent is the presence of __hash__, which clearly can't be used 
in this example), this copying would need to be done for *every* object.

Alternately, the dictionary can say to the API clients, "make an immutable copy 
and supply that instead. It is left to API clients to decide how best to make 
the immutable version". The API client copies the key once (to make the 
immutable version), and everyone lives happily ever after.

For example:

Py> class mylist(list):
...   def __init__(self, arg):
...     super(mylist, self).__init__(arg)
...     self._tuple = None
...   def frozen(self):
...     if self._tuple is None:
...       self._tuple = tuple(self)
...     return self._tuple
...   def unfreeze(self):
...     self._tuple = None
...
Py> x = mylist(range(10))
Py> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Py> x.frozen()
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
Py> x.append(10)
Py> x.frozen()
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
Py> x.unfreeze()
Py> x.frozen()
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

This is much safer than having a list subclass that implements __hash__, and 
still lets you avoid redundant copy operations.

Nicely, so long as you don't unfreeze the object you used to key the dictionary, 
  reusing the same object will always get you the correct dictionary entry, and 
two lists that compare equal at the time they are frozen will also get you the 
same dictionary entry. The equivalent tuples can be used for the lookup, too.

> I also don't want my values to change when I have sorted a list
> and still need to apply a number of algorithms that rely on
> that. Nobody seems to have problems with the possibility that
> the list items are mutable (and called such).

OK, to make this comparison of sorted lists and dictionaries fair:

Write a sorted_list class that is like a regular Python list, but maintains as 
an invariant that the list contents will stay sorted.

See how well you go maintaining that invariant while allowing mutable objects in 
the list. The only way would be to copy them in when they're supplied, and copy 
them out again when you're done. Otherwise, there is no way the class can keep 
its promise. The performance will be lousy, since __setitem__ and __getitem__ 
will be making copies all the time.

Alternatively, the class could declare itself to work reliably only with 
immutable objects. Performance will improve, since copies need only be made when 
an object *actually* changes (and the old immutable copy is deleted and the new 
version inserted in its place).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net



More information about the Python-list mailing list