Pre-PEP: __hashable__

Delaney, Timothy tdelaney at avaya.com
Tue Dec 10 18:00:18 EST 2002


There have been a number of suggestions in the past along the lines of
creating an `immutable()` function.

I would like to propose an alternative, prompted by the new `sets` module in
2.3.

In the `sets` module, when a set is to be put into something like a
dictionary, instead a new immutable set is created which will compare equal
to a set with the same members.

I think this can be generalised, with no loss of functionality or backwards
compatibility.

A new builtin function `hashable(obj)` and a new magic method
`__hashable__(self)` are added.

`hashable(obj)` takes a single object. If the object has a `__hashable__()`
method it is called, and the result returned, otherwise the object itself is
returned:

    def hashable (obj):

        try:
            return obj.__hashable__()
        except AttributeError:
            return self

The object returned from `__hashable__(self)` should be hashable i.e.
`hash(obj)` will not throw an exception. Note though that that is not
enforced above.

The existing `hash(obj)` builtin would be modified as (conceptually):

    def hash (obj):

        try:
            return hashable(obj).__hash__()
        except AttributeError:
            raise TypeError('unhashable type')

What does this gain us? It allows a mutable type to have a snapshot taken an
used as a dictionary key, or other place which requires a hashable instance.
The classic example is a list. Currently if I want to use a list as a
dictionary key, I first have to convert it to a tuple explicitly. The result
of this is that the key no longer compares equal to the original list.

With this proposal, a snapshot of the list could be taken and stored in the
dictionary, without the need to convert it to another type explicitly.

    class immutablelist (tuple):

        def __eq__(self, other):
            return list(self) == other

    class list (object):

        def __hashable__(self):
            return immutablelist(self)

    l = [1, 2, 3]
    d = {}
    d[l] = 1

    for k in d:
        assert k == l

    l[0] = 0

    for k in d:
        assert k != l

    l[0] = 1

    for k in d:
        assert k == l

This is the same behaviour that sets will exhibit (I think I've got the
syntax right, going off memory):

    s = set(1, 2, 3) # at a later date, s = {1, 2, 3}
    d = {}
    d[s] = s

    for k in d:
        assert k == s

    s -= set(1)

    for k in d:
        assert k != s

    s += set(1)

    for k in d:
        assert k == s

The only cases where there is backwards-compatibility issues that I can
think of are cases where currently an exception would be thrown.

Tim Delaney




More information about the Python-list mailing list