Coverage for pycommons / ds / cache.py: 100%

18 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-11 03:04 +0000

1"""A factory for functions checking whether argument values are new.""" 

2from typing import Callable, Final, TypeVar 

3 

4 

5def str_is_new() -> Callable[[str], bool]: 

6 """ 

7 Create a function returning `True` when seeing new `str` values. 

8 

9 Creates a function which returns `True` only the first time it receives a 

10 given string argument and `False` all subsequent times. 

11 This is based on https://stackoverflow.com/questions/27427067 

12 

13 :returns: a function `str_is_new(xx)` that will return `True` the first 

14 time it encounters any value `xx` and `False` for all values it has 

15 already seen 

16 

17 >>> check = str_is_new() 

18 >>> print(check("a")) 

19 True 

20 >>> print(check("a")) 

21 False 

22 >>> print(check("b")) 

23 True 

24 >>> print(check("b")) 

25 False 

26 """ 

27 setdefault: Final[Callable] = {}.setdefault 

28 n = 0 # noqa 

29 

30 def __add(x) -> bool: 

31 nonlocal n 

32 n += 1 

33 return setdefault(x, n) == n 

34 

35 return __add 

36 

37 

38#: a type variable for the representation-base cache 

39#: :func:`pycommons.ds.cache.repr_cache`. 

40T = TypeVar("T") 

41 

42 

43def repr_cache() -> Callable[[T], T]: 

44 """ 

45 Create a cache based on the string representation of an object. 

46 

47 In this type of cache is that the `repr`-representations of objects 

48 are used as keys. The first time an object with a given representation is 

49 encountered, it is stored in the cache and returned. The next time an 

50 object with the same representation is put into this method, the original 

51 object with that representation is returned instead. 

52 

53 This can be used to still cache and canonically retrieve objects which by 

54 themselves are not hashable, like numpy arrays. While the cache itself is 

55 not memory-friendly, it can be used to build data structures that re-use 

56 the same objects again and again. If these data structures are heavily 

57 used, then this can improve the hardware-cache-friendliness of the 

58 corresponding code. 

59 

60 :return: the cache function 

61 :raises TypeError: if the type of a cached object is incompatible with the 

62 type of a requested object 

63 

64 >>> cache: Callable[[object], object] = repr_cache() 

65 

66 >>> a = f"{1 * 5}" 

67 >>> b = "5" 

68 >>> a is b 

69 False 

70 

71 >>> cache(a) 

72 '5' 

73 >>> cache(b) is a 

74 True 

75 

76 >>> x = 5.78 

77 >>> y = 578 / 100 

78 >>> y is x 

79 False 

80 

81 >>> cache(y) 

82 5.78 

83 >>> cache(x) is y 

84 True 

85 

86 >>> class Dummy: 

87 ... def __repr__(self): 

88 ... return "22222" 

89 >>> _ = cache(Dummy()) 

90 >>> try: 

91 ... cache(22222) 

92 ... except TypeError as te: 

93 ... s = str(te) 

94 >>> print(s[:34]) 

95 Cache yields element of wrong type 

96 >>> "Dummy" in s 

97 True 

98 >>> "int" in s 

99 True 

100 """ 

101 setdefault: Final[Callable] = {}.setdefault 

102 

103 def __add(x: T) -> T: 

104 z: Final[T] = setdefault(repr(x), x) 

105 tpe = type(x) 

106 if not isinstance(z, tpe): 

107 raise TypeError("Cache yields element of wrong type " 

108 f"{type(z)}, should be {tpe}.") 

109 return z 

110 

111 return __add