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