1"""Key functions for memoizing decorators."""
2
3from __future__ import absolute_import
4
5__all__ = ('hashkey', 'typedkey')
6
7
8class _HashedTuple(tuple):
9    """A tuple that ensures that hash() will be called no more than once
10    per element, since cache decorators will hash the key multiple
11    times on a cache miss.  See also _HashedSeq in the standard
12    library functools implementation.
13
14    """
15
16    __hashvalue = None
17
18    def __hash__(self, hash=tuple.__hash__):
19        hashvalue = self.__hashvalue
20        if hashvalue is None:
21            self.__hashvalue = hashvalue = hash(self)
22        return hashvalue
23
24    def __add__(self, other, add=tuple.__add__):
25        return _HashedTuple(add(self, other))
26
27    def __radd__(self, other, add=tuple.__add__):
28        return _HashedTuple(add(other, self))
29
30    def __getstate__(self):
31        return {}
32
33
34# used for separating keyword arguments; we do not use an object
35# instance here so identity is preserved when pickling/unpickling
36_kwmark = (_HashedTuple,)
37
38
39def hashkey(*args, **kwargs):
40    """Return a cache key for the specified hashable arguments."""
41
42    if kwargs:
43        return _HashedTuple(args + sum(sorted(kwargs.items()), _kwmark))
44    else:
45        return _HashedTuple(args)
46
47
48def typedkey(*args, **kwargs):
49    """Return a typed cache key for the specified hashable arguments."""
50
51    key = hashkey(*args, **kwargs)
52    key += tuple(type(v) for v in args)
53    key += tuple(type(v) for _, v in sorted(kwargs.items()))
54    return key
55