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   |   [EMAIL PROTECTED]   |   Brisbane, Australia
---------------------------------------------------------------
            http://boredomandlaziness.skystorm.net
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to