There are several standard ways to make a class hashable, for example (borrowing from SO):
# assume X has 2 attributes: attr_a and attr_b
class X:
def __key(self):
return (self.attr_a, self.attr_b)
def __eq__(x, y):
return isinstance(y, x.__class__) and x.__key() == y.__key()
def __hash__(self):
return hash(self.__key())
Now suppose I have many classes that I want to make hashable. They are all immutable, with immutable attributes, and hashing all these attributes in bulk is acceptable (for a class with too many attributes, we would only want to hash a few attributes that are enough to avoid most collisions). Can I avoid writing __key()
method by hand for every class?
Would it be a good idea to make a base class that defines __key()
, __eq__
, and __hash__
for them? In particular, I'm not sure whether finding all the instance attributes that should go into __hash__
is doable. I know this is generally impossible, but in this case we can assume more about the object (e.g., it's immutable - after __init__
is finished, its attributes are all hashable, etc.).
(If the inheritance hierarchy won't work, perhaps a decorator would?)
Instances store their attributes in self.__dict__
:
>>> class Foo(object):
... def __init__(self, foo='bar', spam='eggs'):
... self.foo = foo
... self.spam = spam
...
>>> f = Foo()
>>> f.__dict__
{'foo': 'bar', 'spam': 'eggs'}
Provided you don't store any methods on your instances, a default .__key()
could be:
def __key(self):
return tuple(v for k, v in sorted(self.__dict__.items()))
where we sort the items by attribute name; the tuple()
call ensures we return an immutable sequence suitable for the hash()
call.
For more complex setups you'd have to either test for the types returned by values()
(skip functions and such) or use a specific pattern of attributes or repurpose __slots__
to list the appropriate attributes you can use.
Together with your __hash__
and __eq__
methods, that'd make a good base class to inherit from for all your immutable classes.