Coverage for pycommons / ds / cache.py: 100%
18 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-02 06:36 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-02 06:36 +0000
1"""A factory for functions checking whether argument values are new."""
2from typing import Callable, Final, TypeVar
4#: a type variable for the representation-base cache
5#: :func:`pycommons.ds.cache.repr_cache`.
6T = TypeVar("T")
9def is_new() -> Callable[[str], bool]:
10 """
11 Create a function returning `True` when seeing new values.
13 Creates a function which returns `True` only the first time it receives a
14 given argument and `False` all subsequent times.
15 This is based on https://stackoverflow.com/questions/27427067
17 :returns: a function `is_new(xx)` that will return `True` the first
18 time it encounters any value `xx` and `False` for all values it has
19 already seen
21 >>> check1: Callable[[str], bool] = is_new()
22 >>> print(check1("a"))
23 True
24 >>> print(check1("a"))
25 False
26 >>> print(check1("b"))
27 True
28 >>> print(check1("b"))
29 False
31 >>> check2: Callable[[int], bool] = is_new()
32 >>> print(check2(1))
33 True
34 >>> print(check2(1))
35 False
36 >>> print(check2(3))
37 True
38 >>> print(check2(4))
39 True
40 >>> print(check2(3))
41 False
42 """
43 setdefault: Final[Callable] = {}.setdefault
44 n = 0 # noqa
46 def __add(x) -> bool:
47 nonlocal n
48 n += 1
49 return setdefault(x, n) == n
51 return __add
54def repr_cache() -> Callable[[T], T]:
55 """
56 Create a cache based on the string representation of an object.
58 In this type of cache is that the `repr`-representations of objects
59 are used as keys. The first time an object with a given representation is
60 encountered, it is stored in the cache and returned. The next time an
61 object with the same representation is put into this method, the original
62 object with that representation is returned instead.
64 This can be used to still cache and canonically retrieve objects which by
65 themselves are not hashable, like numpy arrays. While the cache itself is
66 not memory-friendly, it can be used to build data structures that re-use
67 the same objects again and again. If these data structures are heavily
68 used, then this can improve the hardware-cache-friendliness of the
69 corresponding code.
71 :return: the cache function
72 :raises TypeError: if the type of a cached object is incompatible with the
73 type of a requested object
75 >>> cache1: Callable[[str], str] = repr_cache()
76 >>> a = f"{1 * 5}"
77 >>> b = "5"
78 >>> a is b
79 False
81 >>> cache1(a)
82 '5'
83 >>> cache1(b) is a
84 True
86 >>> cache2: Callable[[float], float] = repr_cache()
87 >>> x = 5.78
88 >>> y = 578 / 100
89 >>> y is x
90 False
91 >>> cache2(y)
92 5.78
93 >>> cache2(x) is y
94 True
96 >>> cache3: Callable[[dict[int, str]], dict[int, str]] = repr_cache()
97 >>> a = {1: '1', 3: '3', 7: '7'}
98 >>> b = {1: '1', 3: '3', 7: '7'}
99 >>> print(cache3(a))
100 {1: '1', 3: '3', 7: '7'}
101 >>> print(cache3(a) is a)
102 True
103 >>> print(cache3(b) is a)
104 True
105 >>> print(cache3(b) is b)
106 False
108 >>> class Dummy:
109 ... def __repr__(self):
110 ... return "22222"
111 >>> cache4: Callable[[Dummy], Dummy] = repr_cache()
112 >>> _ = cache4(Dummy())
113 >>> try:
114 ... cache4(22222)
115 ... except TypeError as te:
116 ... s = str(te)
117 >>> print(s[:34])
118 Cache yields element of wrong type
119 >>> "Dummy" in s
120 True
121 >>> "int" in s
122 True
123 """
124 setdefault: Final[Callable] = {}.setdefault
126 def __add(x: T) -> T:
127 z: Final[T] = setdefault(repr(x), x)
128 tpe = type(x)
129 if not isinstance(z, tpe):
130 raise TypeError("Cache yields element of wrong type "
131 f"{type(z)}, should be {tpe}.")
132 return z
134 return __add