passagemath-objects 10.8.1a3__cp314-cp314-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. passagemath_objects/__init__.py +3 -0
  2. passagemath_objects-10.8.1a3.dist-info/DELVEWHEEL +2 -0
  3. passagemath_objects-10.8.1a3.dist-info/METADATA +114 -0
  4. passagemath_objects-10.8.1a3.dist-info/RECORD +283 -0
  5. passagemath_objects-10.8.1a3.dist-info/WHEEL +5 -0
  6. passagemath_objects-10.8.1a3.dist-info/top_level.txt +3 -0
  7. passagemath_objects.libs/libgmp-10-60021eeab4282b29024e43b2b1412b53.dll +0 -0
  8. sage/all__sagemath_objects.py +46 -0
  9. sage/arith/all__sagemath_objects.py +5 -0
  10. sage/arith/long.pxd +411 -0
  11. sage/arith/numerical_approx.cp314-win_amd64.pyd +0 -0
  12. sage/arith/numerical_approx.pxd +35 -0
  13. sage/arith/numerical_approx.pyx +75 -0
  14. sage/arith/power.cp314-win_amd64.pyd +0 -0
  15. sage/arith/power.pxd +31 -0
  16. sage/arith/power.pyx +127 -0
  17. sage/categories/action.cp314-win_amd64.pyd +0 -0
  18. sage/categories/action.pxd +29 -0
  19. sage/categories/action.pyx +641 -0
  20. sage/categories/algebra_functor.py +745 -0
  21. sage/categories/all__sagemath_objects.py +33 -0
  22. sage/categories/basic.py +71 -0
  23. sage/categories/cartesian_product.py +292 -0
  24. sage/categories/category.py +3379 -0
  25. sage/categories/category_cy_helper.cp314-win_amd64.pyd +0 -0
  26. sage/categories/category_cy_helper.pxd +8 -0
  27. sage/categories/category_cy_helper.pyx +322 -0
  28. sage/categories/category_singleton.cp314-win_amd64.pyd +0 -0
  29. sage/categories/category_singleton.pxd +3 -0
  30. sage/categories/category_singleton.pyx +343 -0
  31. sage/categories/category_types.py +637 -0
  32. sage/categories/category_with_axiom.py +2889 -0
  33. sage/categories/covariant_functorial_construction.py +700 -0
  34. sage/categories/facade_sets.py +228 -0
  35. sage/categories/functor.cp314-win_amd64.pyd +0 -0
  36. sage/categories/functor.pxd +7 -0
  37. sage/categories/functor.pyx +659 -0
  38. sage/categories/homset.py +1289 -0
  39. sage/categories/homsets.py +364 -0
  40. sage/categories/isomorphic_objects.py +73 -0
  41. sage/categories/map.cp314-win_amd64.pyd +0 -0
  42. sage/categories/map.pxd +34 -0
  43. sage/categories/map.pyx +2106 -0
  44. sage/categories/morphism.cp314-win_amd64.pyd +0 -0
  45. sage/categories/morphism.pxd +14 -0
  46. sage/categories/morphism.pyx +895 -0
  47. sage/categories/objects.py +167 -0
  48. sage/categories/primer.py +1695 -0
  49. sage/categories/pushout.py +4847 -0
  50. sage/categories/quotients.py +64 -0
  51. sage/categories/realizations.py +200 -0
  52. sage/categories/sets_cat.py +3305 -0
  53. sage/categories/sets_with_partial_maps.py +52 -0
  54. sage/categories/subobjects.py +64 -0
  55. sage/categories/subquotients.py +21 -0
  56. sage/categories/with_realizations.py +311 -0
  57. sage/cpython/__init__.py +22 -0
  58. sage/cpython/_py2_random.py +619 -0
  59. sage/cpython/all.py +3 -0
  60. sage/cpython/atexit.cp314-win_amd64.pyd +0 -0
  61. sage/cpython/atexit.pyx +269 -0
  62. sage/cpython/builtin_types.cp314-win_amd64.pyd +0 -0
  63. sage/cpython/builtin_types.pyx +7 -0
  64. sage/cpython/cython_metaclass.cp314-win_amd64.pyd +0 -0
  65. sage/cpython/cython_metaclass.h +117 -0
  66. sage/cpython/cython_metaclass.pxd +3 -0
  67. sage/cpython/cython_metaclass.pyx +130 -0
  68. sage/cpython/debug.cp314-win_amd64.pyd +0 -0
  69. sage/cpython/debug.pyx +302 -0
  70. sage/cpython/dict_del_by_value.cp314-win_amd64.pyd +0 -0
  71. sage/cpython/dict_del_by_value.pxd +9 -0
  72. sage/cpython/dict_del_by_value.pyx +191 -0
  73. sage/cpython/dict_internal.h +80 -0
  74. sage/cpython/getattr.cp314-win_amd64.pyd +0 -0
  75. sage/cpython/getattr.pxd +9 -0
  76. sage/cpython/getattr.pyx +439 -0
  77. sage/cpython/pycore_long.h +97 -0
  78. sage/cpython/pycore_long.pxd +10 -0
  79. sage/cpython/python_debug.h +44 -0
  80. sage/cpython/python_debug.pxd +47 -0
  81. sage/cpython/pyx_visit.h +13 -0
  82. sage/cpython/string.cp314-win_amd64.pyd +0 -0
  83. sage/cpython/string.pxd +76 -0
  84. sage/cpython/string.pyx +34 -0
  85. sage/cpython/string_impl.h +60 -0
  86. sage/cpython/type.cp314-win_amd64.pyd +0 -0
  87. sage/cpython/type.pxd +2 -0
  88. sage/cpython/type.pyx +40 -0
  89. sage/cpython/wrapperdescr.pxd +67 -0
  90. sage/ext/all__sagemath_objects.py +3 -0
  91. sage/ext/ccobject.h +64 -0
  92. sage/ext/cplusplus.pxd +17 -0
  93. sage/ext/mod_int.h +30 -0
  94. sage/ext/mod_int.pxd +24 -0
  95. sage/ext/stdsage.pxd +39 -0
  96. sage/groups/all__sagemath_objects.py +1 -0
  97. sage/groups/group.cp314-win_amd64.pyd +0 -0
  98. sage/groups/group.pxd +14 -0
  99. sage/groups/group.pyx +296 -0
  100. sage/groups/old.cp314-win_amd64.pyd +0 -0
  101. sage/groups/old.pxd +14 -0
  102. sage/groups/old.pyx +219 -0
  103. sage/libs/all__sagemath_objects.py +3 -0
  104. sage/libs/gmp/__init__.py +1 -0
  105. sage/libs/gmp/all.pxd +6 -0
  106. sage/libs/gmp/binop.pxd +23 -0
  107. sage/libs/gmp/misc.pxd +8 -0
  108. sage/libs/gmp/mpf.pxd +88 -0
  109. sage/libs/gmp/mpn.pxd +57 -0
  110. sage/libs/gmp/mpq.pxd +57 -0
  111. sage/libs/gmp/mpz.pxd +202 -0
  112. sage/libs/gmp/pylong.cp314-win_amd64.pyd +0 -0
  113. sage/libs/gmp/pylong.pxd +12 -0
  114. sage/libs/gmp/pylong.pyx +150 -0
  115. sage/libs/gmp/random.pxd +25 -0
  116. sage/libs/gmp/randomize.pxd +59 -0
  117. sage/libs/gmp/types.pxd +53 -0
  118. sage/libs/gmpxx.pxd +19 -0
  119. sage/misc/abstract_method.py +276 -0
  120. sage/misc/all__sagemath_objects.py +43 -0
  121. sage/misc/bindable_class.py +253 -0
  122. sage/misc/c3_controlled.cp314-win_amd64.pyd +0 -0
  123. sage/misc/c3_controlled.pxd +2 -0
  124. sage/misc/c3_controlled.pyx +1402 -0
  125. sage/misc/cachefunc.cp314-win_amd64.pyd +0 -0
  126. sage/misc/cachefunc.pxd +43 -0
  127. sage/misc/cachefunc.pyx +3801 -0
  128. sage/misc/call.py +188 -0
  129. sage/misc/classcall_metaclass.cp314-win_amd64.pyd +0 -0
  130. sage/misc/classcall_metaclass.pxd +14 -0
  131. sage/misc/classcall_metaclass.pyx +599 -0
  132. sage/misc/constant_function.cp314-win_amd64.pyd +0 -0
  133. sage/misc/constant_function.pyx +130 -0
  134. sage/misc/decorators.py +739 -0
  135. sage/misc/fast_methods.cp314-win_amd64.pyd +0 -0
  136. sage/misc/fast_methods.pxd +20 -0
  137. sage/misc/fast_methods.pyx +351 -0
  138. sage/misc/flatten.py +90 -0
  139. sage/misc/fpickle.cp314-win_amd64.pyd +0 -0
  140. sage/misc/fpickle.pyx +176 -0
  141. sage/misc/function_mangling.cp314-win_amd64.pyd +0 -0
  142. sage/misc/function_mangling.pxd +11 -0
  143. sage/misc/function_mangling.pyx +308 -0
  144. sage/misc/inherit_comparison.cp314-win_amd64.pyd +0 -0
  145. sage/misc/inherit_comparison.pxd +5 -0
  146. sage/misc/inherit_comparison.pyx +105 -0
  147. sage/misc/instancedoc.cp314-win_amd64.pyd +0 -0
  148. sage/misc/instancedoc.pyx +331 -0
  149. sage/misc/lazy_attribute.cp314-win_amd64.pyd +0 -0
  150. sage/misc/lazy_attribute.pyx +607 -0
  151. sage/misc/lazy_format.py +132 -0
  152. sage/misc/lazy_import.cp314-win_amd64.pyd +0 -0
  153. sage/misc/lazy_import.pxd +13 -0
  154. sage/misc/lazy_import.pyx +1307 -0
  155. sage/misc/lazy_import_cache.py +36 -0
  156. sage/misc/lazy_list.cp314-win_amd64.pyd +0 -0
  157. sage/misc/lazy_list.pxd +19 -0
  158. sage/misc/lazy_list.pyx +1187 -0
  159. sage/misc/lazy_string.cp314-win_amd64.pyd +0 -0
  160. sage/misc/lazy_string.pxd +7 -0
  161. sage/misc/lazy_string.pyx +546 -0
  162. sage/misc/misc.py +980 -0
  163. sage/misc/misc_c.cp314-win_amd64.pyd +0 -0
  164. sage/misc/misc_c.pxd +3 -0
  165. sage/misc/misc_c.pyx +765 -0
  166. sage/misc/namespace_package.py +37 -0
  167. sage/misc/nested_class.cp314-win_amd64.pyd +0 -0
  168. sage/misc/nested_class.pxd +3 -0
  169. sage/misc/nested_class.pyx +394 -0
  170. sage/misc/persist.cp314-win_amd64.pyd +0 -0
  171. sage/misc/persist.pyx +1279 -0
  172. sage/misc/prandom.py +418 -0
  173. sage/misc/randstate.cp314-win_amd64.pyd +0 -0
  174. sage/misc/randstate.pxd +31 -0
  175. sage/misc/randstate.pyx +1096 -0
  176. sage/misc/repr.py +203 -0
  177. sage/misc/reset.cp314-win_amd64.pyd +0 -0
  178. sage/misc/reset.pyx +196 -0
  179. sage/misc/sage_ostools.cp314-win_amd64.pyd +0 -0
  180. sage/misc/sage_ostools.pyx +323 -0
  181. sage/misc/sage_timeit.py +275 -0
  182. sage/misc/sage_timeit_class.cp314-win_amd64.pyd +0 -0
  183. sage/misc/sage_timeit_class.pyx +120 -0
  184. sage/misc/sage_unittest.py +639 -0
  185. sage/misc/sageinspect.py +2792 -0
  186. sage/misc/session.cp314-win_amd64.pyd +0 -0
  187. sage/misc/session.pyx +392 -0
  188. sage/misc/superseded.py +576 -0
  189. sage/misc/test_nested_class.py +228 -0
  190. sage/misc/timing.py +264 -0
  191. sage/misc/unknown.py +222 -0
  192. sage/misc/verbose.py +253 -0
  193. sage/misc/weak_dict.cp314-win_amd64.pyd +0 -0
  194. sage/misc/weak_dict.pxd +15 -0
  195. sage/misc/weak_dict.pyx +1197 -0
  196. sage/modules/all__sagemath_objects.py +1 -0
  197. sage/modules/module.cp314-win_amd64.pyd +0 -0
  198. sage/modules/module.pxd +5 -0
  199. sage/modules/module.pyx +329 -0
  200. sage/rings/all__sagemath_objects.py +3 -0
  201. sage/rings/integer_fake.h +22 -0
  202. sage/rings/integer_fake.pxd +55 -0
  203. sage/rings/integer_fake.pyi +8 -0
  204. sage/sets/all__sagemath_objects.py +3 -0
  205. sage/sets/pythonclass.cp314-win_amd64.pyd +0 -0
  206. sage/sets/pythonclass.pxd +9 -0
  207. sage/sets/pythonclass.pyx +247 -0
  208. sage/structure/__init__.py +13 -0
  209. sage/structure/all.py +30 -0
  210. sage/structure/category_object.cp314-win_amd64.pyd +0 -0
  211. sage/structure/category_object.pxd +28 -0
  212. sage/structure/category_object.pyx +1090 -0
  213. sage/structure/coerce.cp314-win_amd64.pyd +0 -0
  214. sage/structure/coerce.pxd +44 -0
  215. sage/structure/coerce.pyx +2113 -0
  216. sage/structure/coerce_actions.cp314-win_amd64.pyd +0 -0
  217. sage/structure/coerce_actions.pxd +27 -0
  218. sage/structure/coerce_actions.pyx +988 -0
  219. sage/structure/coerce_dict.cp314-win_amd64.pyd +0 -0
  220. sage/structure/coerce_dict.pxd +51 -0
  221. sage/structure/coerce_dict.pyx +1557 -0
  222. sage/structure/coerce_exceptions.py +23 -0
  223. sage/structure/coerce_maps.cp314-win_amd64.pyd +0 -0
  224. sage/structure/coerce_maps.pxd +24 -0
  225. sage/structure/coerce_maps.pyx +656 -0
  226. sage/structure/debug_options.cp314-win_amd64.pyd +0 -0
  227. sage/structure/debug_options.pxd +6 -0
  228. sage/structure/debug_options.pyx +54 -0
  229. sage/structure/dynamic_class.py +541 -0
  230. sage/structure/element.cp314-win_amd64.pyd +0 -0
  231. sage/structure/element.pxd +271 -0
  232. sage/structure/element.pyx +4584 -0
  233. sage/structure/element_wrapper.cp314-win_amd64.pyd +0 -0
  234. sage/structure/element_wrapper.pxd +12 -0
  235. sage/structure/element_wrapper.pyx +582 -0
  236. sage/structure/factorization.py +1457 -0
  237. sage/structure/factorization_integer.py +154 -0
  238. sage/structure/factory.cp314-win_amd64.pyd +0 -0
  239. sage/structure/factory.pyx +863 -0
  240. sage/structure/formal_sum.py +489 -0
  241. sage/structure/gens_py.py +73 -0
  242. sage/structure/global_options.py +1725 -0
  243. sage/structure/indexed_generators.py +863 -0
  244. sage/structure/list_clone.cp314-win_amd64.pyd +0 -0
  245. sage/structure/list_clone.pxd +65 -0
  246. sage/structure/list_clone.pyx +1867 -0
  247. sage/structure/list_clone_demo.cp314-win_amd64.pyd +0 -0
  248. sage/structure/list_clone_demo.pyx +248 -0
  249. sage/structure/list_clone_timings.py +179 -0
  250. sage/structure/list_clone_timings_cy.cp314-win_amd64.pyd +0 -0
  251. sage/structure/list_clone_timings_cy.pyx +86 -0
  252. sage/structure/mutability.cp314-win_amd64.pyd +0 -0
  253. sage/structure/mutability.pxd +21 -0
  254. sage/structure/mutability.pyx +346 -0
  255. sage/structure/nonexact.py +69 -0
  256. sage/structure/parent.cp314-win_amd64.pyd +0 -0
  257. sage/structure/parent.pxd +112 -0
  258. sage/structure/parent.pyx +3087 -0
  259. sage/structure/parent_base.cp314-win_amd64.pyd +0 -0
  260. sage/structure/parent_base.pxd +13 -0
  261. sage/structure/parent_base.pyx +35 -0
  262. sage/structure/parent_gens.cp314-win_amd64.pyd +0 -0
  263. sage/structure/parent_gens.pxd +22 -0
  264. sage/structure/parent_gens.pyx +374 -0
  265. sage/structure/parent_old.cp314-win_amd64.pyd +0 -0
  266. sage/structure/parent_old.pxd +24 -0
  267. sage/structure/parent_old.pyx +278 -0
  268. sage/structure/proof/__init__.py +1 -0
  269. sage/structure/proof/all.py +243 -0
  270. sage/structure/proof/proof.py +300 -0
  271. sage/structure/richcmp.cp314-win_amd64.pyd +0 -0
  272. sage/structure/richcmp.pxd +212 -0
  273. sage/structure/richcmp.pyx +494 -0
  274. sage/structure/sage_object.cp314-win_amd64.pyd +0 -0
  275. sage/structure/sage_object.pxd +3 -0
  276. sage/structure/sage_object.pyx +1088 -0
  277. sage/structure/sage_object_test.py +19 -0
  278. sage/structure/sequence.py +937 -0
  279. sage/structure/set_factories.py +1178 -0
  280. sage/structure/set_factories_example.py +527 -0
  281. sage/structure/support_view.py +164 -0
  282. sage/structure/test_factory.py +56 -0
  283. sage/structure/unique_representation.py +1443 -0
@@ -0,0 +1,3801 @@
1
+ # sage_setup: distribution = sagemath-objects
2
+ r"""
3
+ Cached Functions and Methods
4
+
5
+ EXAMPLES:
6
+
7
+ By :issue:`11115`, cached functions and methods are now also
8
+ available in Cython code. The following examples cover various ways
9
+ of usage.
10
+
11
+ Python functions::
12
+
13
+ sage: @cached_function
14
+ ....: def test_pfunc(x):
15
+ ....: '''
16
+ ....: Some documentation
17
+ ....: '''
18
+ ....: return -x
19
+ sage: test_pfunc(5) is test_pfunc(5)
20
+ True
21
+
22
+ In some cases, one would only want to keep the result in cache as long
23
+ as there is any other reference to the result. By :issue:`12215`, this is
24
+ enabled for :class:`~sage.structure.unique_representation.UniqueRepresentation`,
25
+ which is used to create unique parents: If an algebraic structure, such
26
+ as a finite field, is only temporarily used, then it will not stay in
27
+ cache forever. That behaviour is implemented using ``weak_cached_function``,
28
+ that behaves the same as ``cached_function``, except that it uses a
29
+ :class:`~sage.misc.weak_dict.CachedWeakValueDictionary` for storing the results.
30
+
31
+ Cython cdef functions do not allow arbitrary decorators.
32
+ However, one can wrap a Cython function and turn it into
33
+ a cached function, by :issue:`11115`. We need to provide
34
+ the name that the wrapped method or function should have,
35
+ since otherwise the name of the original function would
36
+ be used::
37
+
38
+ sage: # needs sage.misc.cython
39
+ sage: cython('''cpdef test_funct(x): return -x''')
40
+ sage: wrapped_funct = cached_function(test_funct, name='wrapped_funct')
41
+ sage: wrapped_funct
42
+ Cached version of <cyfunction test_funct at ...>
43
+ sage: wrapped_funct.__name__
44
+ 'wrapped_funct'
45
+ sage: wrapped_funct(5)
46
+ -5
47
+ sage: wrapped_funct(5) is wrapped_funct(5)
48
+ True
49
+
50
+ We can proceed similarly for cached methods of Cython classes,
51
+ provided that they allow attribute assignment or have a public
52
+ attribute ``_cached_methods`` of type ``<dict>``. Since
53
+ :issue:`11115`, this is the case for all classes inheriting from
54
+ :class:`~sage.structure.parent.Parent`. See below for a more explicit
55
+ example. By :issue:`12951`, cached methods of extension classes can
56
+ be defined by simply using the decorator. However, an indirect
57
+ approach is still needed for cpdef methods::
58
+
59
+ sage: # needs sage.misc.cython
60
+ sage: cython_code = ['cpdef test_meth(self, x):',
61
+ ....: ' "some doc for a wrapped cython method"',
62
+ ....: ' return -x',
63
+ ....: 'from sage.misc.cachefunc import cached_method',
64
+ ....: 'from sage.structure.parent cimport Parent',
65
+ ....: 'cdef class MyClass(Parent):',
66
+ ....: ' @cached_method',
67
+ ....: ' def direct_method(self, x):',
68
+ ....: ' "Some doc for direct method"',
69
+ ....: ' return 2*x',
70
+ ....: ' wrapped_method = cached_method(test_meth,name="wrapped_method")']
71
+ sage: cython(os.linesep.join(cython_code))
72
+ sage: O = MyClass()
73
+ sage: O.direct_method
74
+ Cached version of <cyfunction MyClass.direct_method at ...>
75
+ sage: O.wrapped_method
76
+ Cached version of <cyfunction test_meth at ...>
77
+ sage: O.wrapped_method.__name__
78
+ 'wrapped_method'
79
+ sage: O.wrapped_method(5)
80
+ -5
81
+ sage: O.wrapped_method(5) is O.wrapped_method(5)
82
+ True
83
+ sage: O.direct_method(5)
84
+ 10
85
+ sage: O.direct_method(5) is O.direct_method(5)
86
+ True
87
+
88
+ By :issue:`11115`, even if a parent does not allow attribute
89
+ assignment, it can inherit a cached method from the parent class of a
90
+ category (previously, the cache would have been broken)::
91
+
92
+ sage: cython_code = ["from sage.misc.cachefunc import cached_method",
93
+ ....: "from sage.misc.cachefunc import cached_in_parent_method",
94
+ ....: "from sage.categories.category import Category",
95
+ ....: "from sage.categories.objects import Objects",
96
+ ....: "class MyCategory(Category):",
97
+ ....: " @cached_method",
98
+ ....: " def super_categories(self):",
99
+ ....: " return [Objects()]",
100
+ ....: " class ElementMethods:",
101
+ ....: " @cached_method",
102
+ ....: " def element_cache_test(self):",
103
+ ....: " return -self",
104
+ ....: " @cached_in_parent_method",
105
+ ....: " def element_via_parent_test(self):",
106
+ ....: " return -self",
107
+ ....: " class ParentMethods:",
108
+ ....: " @cached_method",
109
+ ....: " def one(self):",
110
+ ....: " return self.element_class(self,1)",
111
+ ....: " @cached_method",
112
+ ....: " def invert(self, x):",
113
+ ....: " return -x"]
114
+ sage: cython('\n'.join(cython_code)) # needs sage.misc.cython
115
+ sage: C = MyCategory() # needs sage.misc.cython
116
+
117
+ In order to keep the memory footprint of elements small, it was
118
+ decided to not support the same freedom of using cached methods
119
+ for elements: If an instance of a class derived from
120
+ :class:`~sage.structure.element.Element` does not allow attribute
121
+ assignment, then a cached method inherited from the category of
122
+ its parent will break, as in the class ``MyBrokenElement`` below.
123
+
124
+ However, there is a class :class:`~sage.structure.element.ElementWithCachedMethod`
125
+ that has generally a slower attribute access, but fully supports
126
+ cached methods. We remark, however, that cached methods are
127
+ *much* faster if attribute access works. So, we expect that
128
+ :class:`~sage.structure.element.ElementWithCachedMethod` will
129
+ hardly be used.
130
+ ::
131
+
132
+ sage: # needs sage.misc.cython
133
+ sage: cython_code = ["from sage.structure.element cimport Element, ElementWithCachedMethod", "from cpython.object cimport PyObject_RichCompare",
134
+ ....: "cdef class MyBrokenElement(Element):",
135
+ ....: " cdef public object x",
136
+ ....: " def __init__(self, P, x):",
137
+ ....: " self.x=x",
138
+ ....: " Element.__init__(self,P)",
139
+ ....: " def __neg__(self):",
140
+ ....: " return MyBrokenElement(self.parent(),-self.x)",
141
+ ....: " def _repr_(self):",
142
+ ....: " return '<%s>'%self.x",
143
+ ....: " def __hash__(self):",
144
+ ....: " return hash(self.x)",
145
+ ....: " cpdef _richcmp_(left, right, int op):",
146
+ ....: " return PyObject_RichCompare(left.x, right.x, op)",
147
+ ....: " def raw_test(self):",
148
+ ....: " return -self",
149
+ ....: "cdef class MyElement(ElementWithCachedMethod):",
150
+ ....: " cdef public object x",
151
+ ....: " def __init__(self, P, x):",
152
+ ....: " self.x=x",
153
+ ....: " ElementWithCachedMethod.__init__(self,P)",
154
+ ....: " def __neg__(self):",
155
+ ....: " return MyElement(self.parent(),-self.x)",
156
+ ....: " def _repr_(self):",
157
+ ....: " return '<%s>'%self.x",
158
+ ....: " def __hash__(self):",
159
+ ....: " return hash(self.x)",
160
+ ....: " cpdef _richcmp_(left, right, int op):",
161
+ ....: " return PyObject_RichCompare(left.x, right.x, op)",
162
+ ....: " def raw_test(self):",
163
+ ....: " return -self",
164
+ ....: "from sage.structure.parent cimport Parent",
165
+ ....: "cdef class MyParent(Parent):",
166
+ ....: " Element = MyElement"]
167
+ sage: cython('\n'.join(cython_code))
168
+ sage: P = MyParent(category=C)
169
+ sage: ebroken = MyBrokenElement(P, 5)
170
+ sage: e = MyElement(P, 5)
171
+
172
+ The cached methods inherited by the parent works::
173
+
174
+ sage: # needs sage.misc.cython
175
+ sage: P.one()
176
+ <1>
177
+ sage: P.one() is P.one()
178
+ True
179
+ sage: P.invert(e)
180
+ <-5>
181
+ sage: P.invert(e) is P.invert(e)
182
+ True
183
+
184
+ The cached methods inherited by ``MyElement`` works::
185
+
186
+ sage: # needs sage.misc.cython
187
+ sage: e.element_cache_test()
188
+ <-5>
189
+ sage: e.element_cache_test() is e.element_cache_test()
190
+ True
191
+ sage: e.element_via_parent_test()
192
+ <-5>
193
+ sage: e.element_via_parent_test() is e.element_via_parent_test()
194
+ True
195
+
196
+ The other element class can only inherit a ``cached_in_parent_method``, since
197
+ the cache is stored in the parent. In fact, equal elements share the cache,
198
+ even if they are of different types::
199
+
200
+ sage: e == ebroken # needs sage.misc.cython
201
+ True
202
+ sage: type(e) == type(ebroken) # needs sage.misc.cython
203
+ False
204
+ sage: ebroken.element_via_parent_test() is e.element_via_parent_test() # needs sage.misc.cython
205
+ True
206
+
207
+ However, the cache of the other inherited method breaks, although the method
208
+ as such works::
209
+
210
+ sage: ebroken.element_cache_test() # needs sage.misc.cython
211
+ <-5>
212
+ sage: ebroken.element_cache_test() is ebroken.element_cache_test() # needs sage.misc.cython
213
+ False
214
+
215
+ The cache can be emptied::
216
+
217
+ sage: # needs sage.misc.cython
218
+ sage: a = test_pfunc(5)
219
+ sage: test_pfunc.clear_cache()
220
+ sage: a is test_pfunc(5)
221
+ False
222
+ sage: a = P.one()
223
+ sage: P.one.clear_cache()
224
+ sage: a is P.one()
225
+ False
226
+
227
+ Since ``e`` and ``ebroken`` share the cache, when we empty it for one element
228
+ it is empty for the other as well::
229
+
230
+ sage: b = ebroken.element_via_parent_test() # needs sage.misc.cython
231
+ sage: e.element_via_parent_test.clear_cache() # needs sage.misc.cython
232
+ sage: b is ebroken.element_via_parent_test() # needs sage.misc.cython
233
+ False
234
+
235
+ Introspection works::
236
+
237
+ sage: # needs sage.misc.cython
238
+ sage: from sage.misc.edit_module import file_and_line
239
+ sage: from sage.misc.sageinspect import sage_getdoc, sage_getfile, sage_getsource
240
+ sage: print(sage_getdoc(test_pfunc))
241
+ Some documentation
242
+ sage: print(sage_getdoc(O.wrapped_method))
243
+ some doc for a wrapped cython method
244
+ <BLANKLINE>
245
+ sage: print(sage_getdoc(O.direct_method))
246
+ Some doc for direct method
247
+ <BLANKLINE>
248
+ sage: print(sage_getsource(O.wrapped_method))
249
+ cpdef test_meth(self, x):
250
+ "some doc for a wrapped cython method"
251
+ return -x
252
+ sage: print(sage_getsource(O.direct_method))
253
+ @cached_method
254
+ def direct_method(self, x):
255
+ "Some doc for direct method"
256
+ return 2*x
257
+
258
+ It is a very common special case to cache a method that has no
259
+ arguments. In that special case, the time needed to access the cache
260
+ can be drastically reduced by using a special implementation. The
261
+ cached method decorator automatically determines which implementation
262
+ ought to be chosen. A typical example is
263
+ :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal.gens`
264
+ (no arguments) versus
265
+ :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal.groebner_basis`
266
+ (several arguments)::
267
+
268
+ sage: P.<a,b,c,d> = QQ[]
269
+ sage: I = P * [a, b]
270
+ sage: I.gens()
271
+ [a, b]
272
+ sage: I.gens() is I.gens()
273
+ True
274
+ sage: I.groebner_basis() # needs sage.libs.singular
275
+ [a, b]
276
+ sage: I.groebner_basis() is I.groebner_basis() # needs sage.libs.singular
277
+ True
278
+ sage: type(I.gens)
279
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
280
+ sage: type(I.groebner_basis)
281
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
282
+
283
+ By :issue:`12951`, the cached_method decorator is also supported on non-c(p)def
284
+ methods of extension classes, as long as they either support attribute assignment
285
+ or have a public attribute of type ``<dict>`` called ``_cached_methods``. The
286
+ latter is easy::
287
+
288
+ sage: # needs sage.misc.cython
289
+ sage: cython_code = [
290
+ ....: "from sage.misc.cachefunc import cached_method",
291
+ ....: "cdef class MyClass:",
292
+ ....: " cdef public dict _cached_methods",
293
+ ....: " @cached_method",
294
+ ....: " def f(self, a, b):",
295
+ ....: " return a*b"]
296
+ sage: cython(os.linesep.join(cython_code))
297
+ sage: P = MyClass()
298
+ sage: P.f(2, 3)
299
+ 6
300
+ sage: P.f(2, 3) is P.f(2, 3)
301
+ True
302
+
303
+ Providing attribute access is a bit more tricky, since it is needed that
304
+ an attribute inherited by the instance from its class can be overridden
305
+ on the instance. That is why providing a ``__getattr__`` would not be
306
+ enough in the following example::
307
+
308
+ sage: # needs sage.misc.cython
309
+ sage: cython_code = [
310
+ ....: "from sage.misc.cachefunc import cached_method",
311
+ ....: "cdef class MyOtherClass:",
312
+ ....: " cdef dict D",
313
+ ....: " def __init__(self):",
314
+ ....: " self.D = {}",
315
+ ....: " def __setattr__(self, n, v):",
316
+ ....: " self.D[n] = v",
317
+ ....: " def __getattribute__(self, n):",
318
+ ....: " try:",
319
+ ....: " return self.D[n]",
320
+ ....: " except KeyError:",
321
+ ....: " pass",
322
+ ....: " return getattr(type(self),n).__get__(self)",
323
+ ....: " @cached_method",
324
+ ....: " def f(self, a, b):",
325
+ ....: " return a+b"]
326
+ sage: cython(os.linesep.join(cython_code))
327
+ sage: Q = MyOtherClass()
328
+ sage: Q.f(2, 3)
329
+ 5
330
+ sage: Q.f(2, 3) is Q.f(2, 3)
331
+ True
332
+
333
+ Note that supporting attribute access is somehow faster than the
334
+ easier method::
335
+
336
+ sage: timeit("a = P.f(2,3)") # random # needs sage.misc.cython
337
+ 625 loops, best of 3: 1.3 µs per loop
338
+ sage: timeit("a = Q.f(2,3)") # random # needs sage.misc.cython
339
+ 625 loops, best of 3: 931 ns per loop
340
+
341
+ Some immutable objects (such as `p`-adic numbers) cannot implement a
342
+ reasonable hash function because their ``==`` operator has been
343
+ modified to return ``True`` for objects which might behave differently
344
+ in some computations::
345
+
346
+ sage: # needs sage.rings.padics
347
+ sage: K.<a> = Qq(9)
348
+ sage: b = a.add_bigoh(1)
349
+ sage: c = a + 3
350
+ sage: b
351
+ a + O(3)
352
+ sage: c
353
+ a + 3 + O(3^20)
354
+ sage: b == c
355
+ True
356
+ sage: b == a
357
+ True
358
+ sage: c == a
359
+ False
360
+
361
+ If such objects defined a non-trivial hash function, this would break
362
+ caching in many places. However, such objects should still be usable
363
+ in caches. This can be achieved by defining an appropriate method
364
+ ``_cache_key``::
365
+
366
+ sage: # needs sage.rings.padics
367
+ sage: hash(b)
368
+ Traceback (most recent call last):
369
+ ...
370
+ TypeError: ...unhashable type: 'sage.rings.padics.qadic_flint_CR.qAdicCappedRelativeElement'...
371
+ sage: from sage.misc.cachefunc import cached_method
372
+ sage: @cached_method
373
+ ....: def f(x): return x == a
374
+ sage: f(b)
375
+ True
376
+ sage: f(c) # if b and c were hashable, this would return True
377
+ False
378
+ sage: b._cache_key()
379
+ (..., ((0, 1),), 0, 1)
380
+ sage: c._cache_key()
381
+ (..., ((0, 1), (1,)), 0, 20)
382
+
383
+ .. NOTE::
384
+
385
+ This attribute will only be accessed if the object itself
386
+ is not hashable.
387
+
388
+ An implementation must make sure that for elements ``a`` and ``b``,
389
+ if ``a != b``, then also ``a._cache_key() != b._cache_key()``.
390
+ In practice this means that the ``_cache_key`` should always include
391
+ the parent as its first argument::
392
+
393
+ sage: S.<a> = Qq(4) # needs sage.rings.padics
394
+ sage: d = a.add_bigoh(1) # needs sage.rings.padics
395
+ sage: b._cache_key() == d._cache_key() # this would be True if the parents were not included
396
+ False
397
+
398
+ Note that shallow copy of mutable objects may behave unexpectedly::
399
+
400
+ sage: class Foo:
401
+ ....: @cached_method
402
+ ....: def f(self):
403
+ ....: return self.x
404
+ sage: from copy import copy, deepcopy
405
+ sage: a = Foo()
406
+ sage: a.x = 1
407
+ sage: a.f()
408
+ 1
409
+ sage: b = copy(a)
410
+ sage: b.x = 2
411
+ sage: b.f() # incorrect!
412
+ 1
413
+ sage: b.f is a.f # this is the problem
414
+ True
415
+ sage: b = deepcopy(a)
416
+ sage: b.x = 2
417
+ sage: b.f() # correct
418
+ 2
419
+ sage: b.f is a.f
420
+ False
421
+
422
+ AUTHORS:
423
+
424
+ - William Stein: initial version, (inspired by conversation with Justin Walker)
425
+ - Mike Hansen: added doctests and made it work with class methods
426
+ - Willem Jan Palenstijn: added ``CachedMethodCaller`` for binding cached methods to
427
+ instances
428
+ - Tom Boothby: added ``DiskCachedFunction``
429
+ - Simon King: improved performance, more doctests, cython version,
430
+ ``CachedMethodCallerNoArgs``, weak cached function, cached special methods
431
+ - Julian Rueth (2014-03-19, 2014-05-09, 2014-05-12): added ``key`` parameter, allow caching
432
+ for unhashable elements, added ``do_pickle`` parameter
433
+ - Kwankyu Lee (2025-10): added ``is_pickled_with_cache`` method
434
+ """
435
+ # ****************************************************************************
436
+ # Copyright (C) 2008 William Stein <wstein@gmail.com>
437
+ # Mike Hansen <mhansen@gmail.com>
438
+ # 2011 Simon King <simon.king@uni-jena.de>
439
+ # 2014 Julian Rueth <julian.rueth@fsfe.org>
440
+ # 2015 Jeroen Demeyer <jdemeyer@cage.ugent.be>
441
+ #
442
+ # This program is free software: you can redistribute it and/or modify
443
+ # it under the terms of the GNU General Public License as published by
444
+ # the Free Software Foundation, either version 2 of the License, or
445
+ # (at your option) any later version.
446
+ # https://www.gnu.org/licenses/
447
+ # ****************************************************************************
448
+
449
+ cdef extern from "methodobject.h":
450
+ cdef int METH_NOARGS, METH_O
451
+ cdef int PyCFunction_GetFlags(object op) except -1
452
+
453
+ from collections.abc import Iterator
454
+ from inspect import isfunction
455
+ import os
456
+
457
+ from sage.misc.sageinspect import sage_getfile_relative, sage_getsourcelines, sage_getargspec
458
+
459
+ from sage.misc.weak_dict cimport CachedWeakValueDictionary
460
+ from sage.misc.decorators import decorator_keywords
461
+
462
+ cdef frozenset special_method_names = frozenset(
463
+ ['__abs__', '__add__',
464
+ '__and__', '__call__', '__cmp__', '__coerce__', '__complex__', '__contains__', '__del__',
465
+ '__delattr__', '__delete__', '__delitem__', '__delslice__', '__dir__', '__div__',
466
+ '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__get__', '__getattr__',
467
+ '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__hex__',
468
+ '__iadd__', '__iand__', '__idiv__', '__ifloordiv__', '__ilshift__', '__imod__', '__imul__',
469
+ '__index__', '__init__', '__instancecheck__', '__int__', '__invert__', '__ior__', '__ipow__',
470
+ '__irshift__', '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__',
471
+ '__length_hint__', '__long__', '__lshift__', '__lt__', '__missing__', '__mod__', '__mul__',
472
+ '__ne__', '__neg__', '__new__', '__oct__', '__or__', '__pos__', '__pow__',
473
+ '__radd__', '__rand__', '__rdiv__', '__repr__', '__reversed__', '__rfloordiv__', '__rlshift__',
474
+ '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
475
+ '__rtruediv__', '__rxor__', '__set__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__',
476
+ '__str__', '__sub__', '__subclasscheck__', '__truediv__', '__unicode__', '__xor__', 'next'])
477
+
478
+
479
+ def _cached_function_unpickle(module, name, cache=None):
480
+ """
481
+ Unpickle the cache function ``name`` defined in ``module``.
482
+
483
+ This function loads ``name`` from ``module`` (it does not restore the code
484
+ of the actual function when it was pickled.) The cache is restored from
485
+ ``cache`` if present.
486
+
487
+ INPUT:
488
+
489
+ - ``module`` -- the name of the module to import the function from
490
+ - ``name`` -- the name of the cached function
491
+ - ``cache`` -- list of cached key value pairs
492
+
493
+ TESTS::
494
+
495
+ sage: type(hilbert_class_polynomial) # needs sage.schemes
496
+ <class 'sage.misc.cachefunc.CachedFunction'>
497
+ sage: loads(dumps(hilbert_class_polynomial)) is hilbert_class_polynomial #indirect doctest # needs sage.schemes
498
+ True
499
+
500
+ Verify that the ``cache`` parameter works::
501
+
502
+ sage: @cached_function(do_pickle=True)
503
+ ....: def f(n): return n
504
+ sage: import __main__
505
+ sage: __main__.f = f
506
+ sage: f(0)
507
+ 0
508
+ sage: ((0,),()) in f.cache
509
+ True
510
+
511
+ sage: s = dumps(f)
512
+ sage: f.clear_cache()
513
+ sage: ((0,),()) in f.cache
514
+ False
515
+ sage: f = loads(s)
516
+ sage: ((0,),()) in f.cache
517
+ True
518
+ sage: f(0)
519
+ 0
520
+ """
521
+ ret = getattr(__import__(module, fromlist=['']), name)
522
+ if cache is not None:
523
+ ret.cache.update(cache)
524
+ return ret
525
+
526
+
527
+ cdef class NonpicklingDict(dict):
528
+ r"""
529
+ A special dict which does not pickle its contents.
530
+
531
+ EXAMPLES::
532
+
533
+ sage: from sage.misc.cachefunc import NonpicklingDict
534
+ sage: d = NonpicklingDict()
535
+ sage: d[0] = 0
536
+ sage: loads(dumps(d))
537
+ {}
538
+ """
539
+ def __reduce__(self):
540
+ r"""
541
+ Return data required to unpickle this dictionary.
542
+
543
+ EXAMPLES::
544
+
545
+ sage: from sage.misc.cachefunc import NonpicklingDict
546
+ sage: d = NonpicklingDict()
547
+ sage: d[0] = 0
548
+ sage: d.__reduce__()
549
+ (<class 'sage.misc.cachefunc.NonpicklingDict'>, ())
550
+ """
551
+ return NonpicklingDict, ()
552
+
553
+
554
+ cdef unhashable_key = object()
555
+
556
+ cpdef inline dict_key(o):
557
+ """
558
+ Return a key to cache object ``o`` in a dict.
559
+
560
+ This is different from ``cache_key`` since the ``cache_key`` might
561
+ get confused with the key of a hashable object. Therefore, such keys
562
+ include ``unhashable_key`` which acts as a unique marker which is
563
+ certainly not stored in the dictionary otherwise.
564
+
565
+ EXAMPLES::
566
+
567
+ sage: from sage.misc.cachefunc import dict_key
568
+ sage: dict_key(42)
569
+ 42
570
+ sage: K.<u> = Qq(9) # needs sage.rings.padics
571
+ sage: dict_key(u) # needs sage.rings.padics
572
+ (<object object at ...>, (..., 20))
573
+ """
574
+ try:
575
+ hash(o)
576
+ except TypeError:
577
+ o = (unhashable_key, cache_key_unhashable(o))
578
+ return o
579
+
580
+
581
+ cpdef inline cache_key(o):
582
+ r"""
583
+ Helper function to return a hashable key for ``o`` which can be used for
584
+ caching.
585
+
586
+ This function is intended for objects which are not hashable such as
587
+ `p`-adic numbers. The difference from calling an object's ``_cache_key``
588
+ method directly, is that it also works for tuples and unpacks them
589
+ recursively (if necessary, i.e., if they are not hashable).
590
+
591
+ EXAMPLES::
592
+
593
+ sage: from sage.misc.cachefunc import cache_key
594
+ sage: K.<u> = Qq(9) # needs sage.rings.padics
595
+ sage: a = K(1); a # needs sage.rings.padics
596
+ 1 + O(3^20)
597
+ sage: cache_key(a) # needs sage.rings.padics
598
+ (..., ((1,),), 0, 20)
599
+
600
+ This function works if ``o`` is a tuple. In this case it unpacks its
601
+ entries recursively::
602
+
603
+ sage: o = (1, 2, (3, a)) # needs sage.rings.padics
604
+ sage: cache_key(o) # needs sage.rings.padics
605
+ (1, 2, (3, (..., ((1,),), 0, 20)))
606
+
607
+ Note that tuples are only partially unpacked if some of its entries are
608
+ hashable::
609
+
610
+ sage: o = (1/2, a) # needs sage.rings.padics
611
+ sage: cache_key(o) # needs sage.rings.padics
612
+ (1/2, (..., ((1,),), 0, 20))
613
+ """
614
+ try:
615
+ hash(o)
616
+ except TypeError:
617
+ o = cache_key_unhashable(o)
618
+ return o
619
+
620
+
621
+ cdef cache_key_unhashable(o):
622
+ """
623
+ Return a key for caching an item which is unhashable.
624
+ """
625
+ if isinstance(o, tuple):
626
+ return tuple(cache_key(item) for item in o)
627
+ try:
628
+ k = o._cache_key()
629
+ except AttributeError:
630
+ raise TypeError("unhashable type: {!r}".format(type(o).__name__))
631
+ return cache_key(k)
632
+
633
+
634
+ cdef class CachedFunction():
635
+ """
636
+ Create a cached version of a function, which only recomputes
637
+ values it hasn't already computed. Synonyme: ``cached_function``
638
+
639
+ INPUT:
640
+
641
+ - ``f`` -- a function
642
+ - ``name`` -- (optional string) name that the cached version
643
+ of ``f`` should be provided with
644
+ - ``key`` -- (optional callable) takes the input and returns a
645
+ key for the cache, typically one would use this to normalize input
646
+ - ``do_pickle`` -- (optional boolean) whether or not the contents of the
647
+ cache should be included when pickling this function; the default is not
648
+ to include them.
649
+
650
+ If ``f`` is a function, do either ``g = CachedFunction(f)``
651
+ or ``g = cached_function(f)`` to make a cached version of ``f``,
652
+ or put ``@cached_function`` right before the definition of ``f``
653
+ (i.e., use Python decorators)::
654
+
655
+ @cached_function
656
+ def f(...):
657
+ ....
658
+
659
+ The inputs to the function must be hashable or they must define
660
+ :meth:`sage.structure.sage_object.SageObject._cache_key`.
661
+
662
+ EXAMPLES::
663
+
664
+ sage: @cached_function
665
+ ....: def mul(x, y=2):
666
+ ....: return x*y
667
+ sage: mul(3)
668
+ 6
669
+
670
+ We demonstrate that the result is cached, and that, moreover,
671
+ the cache takes into account the various ways of providing
672
+ default arguments::
673
+
674
+ sage: mul(3) is mul(3,2)
675
+ True
676
+ sage: mul(3,y=2) is mul(3,2)
677
+ True
678
+
679
+ The user can clear the cache::
680
+
681
+ sage: a = mul(4)
682
+ sage: mul.clear_cache()
683
+ sage: a is mul(4)
684
+ False
685
+
686
+ It is also possible to explicitly override the cache with
687
+ a different value::
688
+
689
+ sage: mul.set_cache('foo',5)
690
+ sage: mul(5,2)
691
+ 'foo'
692
+
693
+ The parameter ``key`` can be used to ignore parameters for
694
+ caching. In this example we ignore the parameter ``algorithm``::
695
+
696
+ sage: @cached_function(key=lambda x,y,algorithm: (x,y))
697
+ ....: def mul(x, y, algorithm='default'):
698
+ ....: return x*y
699
+ sage: mul(1,1,algorithm='default') is mul(1,1,algorithm='algorithm') is mul(1,1) is mul(1,1,'default')
700
+ True
701
+ """
702
+ def __init__(self, f, *, classmethod=False, name: str | None = None, key=None, do_pickle: bool = False) -> None:
703
+ """
704
+ Create a cached version of a function, which only recomputes
705
+ values it hasn't already computed. A custom name can be
706
+ provided by an optional argument "name".
707
+
708
+ If ``f`` is a function, do either ``g = CachedFunction(f)``
709
+ to make a cached version of ``f``, or put ``@CachedFunction``
710
+ right before the definition of ``f`` (i.e., use Python decorators)::
711
+
712
+ @CachedFunction
713
+ def f(...):
714
+ ....
715
+
716
+ The inputs to the function must be hashable or they must define
717
+ :meth:`sage.structure.sage_object.SageObject._cache_key`.
718
+
719
+ TESTS::
720
+
721
+ sage: # needs sage.combinat
722
+ sage: g = CachedFunction(number_of_partitions)
723
+ sage: g.__name__
724
+ 'number_of_partitions'
725
+ sage: 'partitions' in sage.misc.sageinspect.sage_getdoc(g)
726
+ True
727
+ sage: g(5) # needs sage.libs.flint
728
+ 7
729
+ sage: g.cache # needs sage.libs.flint
730
+ {((5, 'default'), ()): 7}
731
+
732
+ sage: def f(t=1): print(t)
733
+ sage: h = CachedFunction(f)
734
+ sage: w = walltime()
735
+ sage: h(); h(1); h(t=1)
736
+ 1
737
+ sage: walltime(w) < 2
738
+ True
739
+
740
+ By default, the contents of the cache are not pickled::
741
+
742
+ sage: @cached_function
743
+ ....: def f(n): return None
744
+ sage: import __main__
745
+ sage: __main__.f = f
746
+ sage: for i in range(100): f(i)
747
+ sage: len(f.cache)
748
+ 100
749
+
750
+ sage: s = dumps(f)
751
+ sage: f.clear_cache()
752
+ sage: f = loads(s)
753
+ sage: len(f.cache)
754
+ 0
755
+
756
+ If ``do_pickle`` is set, then the cache is pickled::
757
+
758
+ sage: @cached_function(do_pickle=True)
759
+ ....: def f(n): return None
760
+ sage: __main__.f = f
761
+ sage: for i in range(100): f(i)
762
+ sage: len(f.cache)
763
+ 100
764
+
765
+ sage: s = dumps(f)
766
+ sage: f.clear_cache()
767
+ sage: f = loads(s)
768
+ sage: len(f.cache)
769
+ 100
770
+ """
771
+ self.is_classmethod = classmethod
772
+ self._common_init(f, None, name=name, key=key, do_pickle=do_pickle)
773
+ self.cache = {} if do_pickle else NonpicklingDict()
774
+
775
+ def _common_init(self, f, argument_fixer, name: str | None = None, key=None, do_pickle: bool = False) -> None:
776
+ """
777
+ Perform initialization common to CachedFunction and CachedMethodCaller.
778
+
779
+ TESTS::
780
+
781
+ sage: @cached_function
782
+ ....: def test_cache(x):
783
+ ....: return -x
784
+ sage: test_cache.__name__ # indirect doctest
785
+ 'test_cache'
786
+ """
787
+ self.f = f
788
+ self.key = key
789
+ self.do_pickle = do_pickle
790
+ if name is not None:
791
+ self.__name__ = name
792
+ else:
793
+ self.__name__ = f.__name__
794
+ try:
795
+ self.__cached_module__ = f.__module__
796
+ except AttributeError:
797
+ self.__cached_module__ = f.__objclass__.__module__
798
+ if argument_fixer is not None:
799
+ # it is None unless the argument fixer
800
+ # was known previously. See #15038.
801
+ self._argument_fixer = argument_fixer
802
+
803
+ @property
804
+ def __module__(self) -> str:
805
+ return self.__cached_module__
806
+
807
+ cdef get_key_args_kwds(self, tuple args, dict kwds):
808
+ """
809
+ Return the key in the cache to be used when ``args`` and
810
+ ``kwds`` are passed in as parameters.
811
+
812
+ See ``get_key`` for the Python interface and tests.
813
+ """
814
+ # The key for "no arguments" is cached in empty_key
815
+ if not args and not kwds:
816
+ if self.empty_key is None:
817
+ self.empty_key = self.fix_args_kwds(args, kwds)
818
+ return self.empty_key
819
+
820
+ return self.fix_args_kwds(args, kwds)
821
+
822
+ cdef int argfix_init(self) except -1:
823
+ """
824
+ TESTS::
825
+
826
+ sage: @cached_function
827
+ ....: def test_cache(x):
828
+ ....: return -x
829
+ sage: test_cache(1)
830
+ -1
831
+ """
832
+ self._argument_fixer = ArgumentFixer(self.f,
833
+ classmethod=self.is_classmethod)
834
+
835
+ cdef fix_args_kwds(self, tuple args, dict kwds):
836
+ r"""
837
+ Normalize parameters to obtain a key for the cache.
838
+
839
+ TESTS::
840
+
841
+ sage: @cached_function(key=lambda x,y,algorithm: (x,y))
842
+ ....: def mul(x, y, algorithm='default'):
843
+ ....: return x*y
844
+ sage: mul.get_key(1,1,"default") # indirect doctest
845
+ (1, 1)
846
+ """
847
+ if self._argument_fixer is None:
848
+ self.argfix_init()
849
+
850
+ k = self._argument_fixer.fix_to_pos_args_kwds(args, kwds)
851
+ if self.key is None:
852
+ return k
853
+ else:
854
+ return self.key(*k[0], **dict(k[1]))
855
+
856
+ def __reduce__(self):
857
+ """
858
+ Pickling of cached functions.
859
+
860
+ TESTS::
861
+
862
+ sage: type(hilbert_class_polynomial) # needs sage.schemes
863
+ <class 'sage.misc.cachefunc.CachedFunction'>
864
+ sage: loads(dumps(hilbert_class_polynomial)) is hilbert_class_polynomial #indirect doctest # needs sage.schemes
865
+ True
866
+ """
867
+ return _cached_function_unpickle, (self.__cached_module__, self.__name__, self.cache)
868
+
869
+ def is_pickled_with_cache(self):
870
+ """
871
+ Return ``True`` if this cached function is pickled with the cache.
872
+
873
+ EXAMPLES::
874
+
875
+ sage: @cached_function
876
+ ....: def f(x):
877
+ ....: return x
878
+ ....:
879
+ sage: f.is_pickled_with_cache()
880
+ False
881
+ sage: @cached_function(do_pickle=True)
882
+ ....: def f(x):
883
+ ....: return x
884
+ ....:
885
+ sage: f.is_pickled_with_cache()
886
+ True
887
+ """
888
+ return self.do_pickle
889
+
890
+ # Introspection
891
+ #
892
+ # We provide some methods explicitly, and forward
893
+ # other questions to the cached function.
894
+
895
+ def _instancedoc_(self):
896
+ """
897
+ Provide documentation for the cached function.
898
+
899
+ A cached function shall inherit the documentation
900
+ from the function that is wrapped, not from the
901
+ documentation of the wrapper.
902
+
903
+ TESTS::
904
+
905
+ sage: P.<x,y> = QQ[]
906
+ sage: I = P * [x,y]
907
+ sage: from sage.misc.sageinspect import sage_getdoc
908
+ sage: print(sage_getdoc(I.groebner_basis)) # indirect doctest
909
+ WARNING: the enclosing module is marked...
910
+ Return the reduced Groebner basis of this ideal.
911
+ ...
912
+
913
+ Test that :issue:`15184` is fixed::
914
+
915
+ sage: from sage.misc.sageinspect import sage_getfile
916
+ sage: type(I.groebner_basis)
917
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
918
+ sage: os.path.exists(sage_getfile(I.groebner_basis))
919
+ True
920
+
921
+ Test that :issue:`18064` is fixed::
922
+
923
+ sage: @cached_function
924
+ ....: def f():
925
+ ....: return 3
926
+ sage: f.__doc__
927
+ 'File: ... (starting at line 1)\n'
928
+ """
929
+ from sage.misc.sageinspect import _extract_embedded_position
930
+ f = self.f
931
+ doc = f.__doc__ or ''
932
+ if not doc or _extract_embedded_position(doc) is None:
933
+ try:
934
+ sourcelines = sage_getsourcelines(f)
935
+ filename = sage_getfile_relative(f)
936
+ # this is a rather expensive way of getting the line number, because
937
+ # retrieving the source requires reading the source file and in many
938
+ # cases this is not required (in cython it's embedded in the docstring,
939
+ # on code objects you'll find it in co_filename and co_firstlineno)
940
+ # however, this hasn't been factored out yet in sageinspect
941
+ # and the logic in sage_getsourcelines is rather intricate.
942
+ file_info = "File: {} (starting at line {})".format(filename, sourcelines[1]) + os.linesep
943
+
944
+ doc = file_info + doc
945
+ except IOError:
946
+ pass
947
+ return doc
948
+
949
+ def _sage_src_(self):
950
+ """
951
+ Return the source code for the wrapped function.
952
+
953
+ TESTS::
954
+
955
+ sage: from sage.misc.sageinspect import sage_getsource
956
+ sage: g = CachedFunction(number_of_partitions)
957
+ sage: 'flint' in sage_getsource(g) # indirect doctest
958
+ True
959
+ """
960
+ from sage.misc.sageinspect import sage_getsource
961
+ return sage_getsource(self.f)
962
+
963
+ def _sage_src_lines_(self):
964
+ r"""
965
+ Return the list of source lines and the first line number
966
+ of the wrapped function.
967
+
968
+ TESTS::
969
+
970
+ sage: P.<x,y> = QQ[]
971
+ sage: I = P*[x,y]
972
+ sage: from sage.misc.sageinspect import sage_getsourcelines
973
+ sage: l = ' elif algorithm.startswith("macaulay2:"):\n'
974
+ sage: l in sage_getsourcelines(I.groebner_basis)[0] # indirect doctest
975
+ True
976
+ """
977
+ from sage.misc.sageinspect import sage_getsourcelines
978
+ return sage_getsourcelines(self.f)
979
+
980
+ def _sage_argspec_(self):
981
+ """
982
+ Return the argspec of the wrapped function or method.
983
+
984
+ This was implemented in :issue:`11115`.
985
+
986
+ EXAMPLES::
987
+
988
+ sage: P.<x,y> = QQ[]
989
+ sage: I = P*[x,y]
990
+ sage: from sage.misc.sageinspect import sage_getargspec
991
+ sage: sage_getargspec(I.groebner_basis) # indirect doctest
992
+ FullArgSpec(args=['self', 'algorithm', 'deg_bound', 'mult_bound', 'prot'],
993
+ varargs='args', varkw='kwds', defaults=('', None, None, False),
994
+ kwonlyargs=[], kwonlydefaults=None, annotations={})
995
+ """
996
+ return sage_getargspec(self.f)
997
+
998
+ def __call__(self, *args, **kwds):
999
+ """
1000
+ Return value from cache or call the wrapped function,
1001
+ caching the output.
1002
+
1003
+ TESTS::
1004
+
1005
+ sage: # needs sage.combinat sage.libs.flint
1006
+ sage: g = CachedFunction(number_of_partitions)
1007
+ sage: a = g(5)
1008
+ sage: g.cache
1009
+ {((5, 'default'), ()): 7}
1010
+ sage: a = g(10^5) # indirect doctest
1011
+ sage: a == number_of_partitions(10^5)
1012
+ True
1013
+ sage: a is g(10^5)
1014
+ True
1015
+ sage: a is number_of_partitions(10^5)
1016
+ True
1017
+
1018
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1019
+ immutable unhashable objects which define
1020
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1021
+
1022
+ sage: # needs sage.rings.padics
1023
+ sage: @cached_function
1024
+ ....: def f(x): return x+x
1025
+ sage: K.<u> = Qq(4)
1026
+ sage: x = K(1,1); x
1027
+ 1 + O(2)
1028
+ sage: y = K(1,2); y
1029
+ 1 + O(2^2)
1030
+ sage: x == y
1031
+ True
1032
+ sage: f(x) is f(x)
1033
+ True
1034
+ sage: f(y) is not f(x)
1035
+ True
1036
+ """
1037
+ k = self.get_key_args_kwds(args, kwds)
1038
+
1039
+ try:
1040
+ try:
1041
+ return self.cache[k]
1042
+ except TypeError: # k is not hashable
1043
+ k = dict_key(k)
1044
+ return self.cache[k]
1045
+ except KeyError:
1046
+ w = self.f(*args, **kwds)
1047
+ self.cache[k] = w
1048
+ return w
1049
+
1050
+ def cached(self, *args, **kwds):
1051
+ """
1052
+ Return the result from the cache if available. If the value is
1053
+ not cached, raise :exc:`KeyError`.
1054
+
1055
+ EXAMPLES::
1056
+
1057
+ sage: @cached_function
1058
+ ....: def f(x):
1059
+ ....: return x
1060
+ sage: f.cached(5)
1061
+ Traceback (most recent call last):
1062
+ ...
1063
+ KeyError: ((5,), ())
1064
+ sage: f(5)
1065
+ 5
1066
+ sage: f.cached(5)
1067
+ 5
1068
+ """
1069
+ k = self.get_key_args_kwds(args, kwds)
1070
+
1071
+ try:
1072
+ return self.cache[k]
1073
+ except TypeError: # k is not hashable
1074
+ k = dict_key(k)
1075
+ return self.cache[k]
1076
+
1077
+ def is_in_cache(self, *args, **kwds):
1078
+ """
1079
+ Check if the argument list is in the cache.
1080
+
1081
+ EXAMPLES::
1082
+
1083
+ sage: class Foo:
1084
+ ....: def __init__(self, x):
1085
+ ....: self._x = x
1086
+ ....: @cached_method
1087
+ ....: def f(self, z, y=0):
1088
+ ....: return self._x*z+y
1089
+ sage: a = Foo(2)
1090
+ sage: a.f.is_in_cache(3)
1091
+ False
1092
+ sage: a.f(3)
1093
+ 6
1094
+ sage: a.f.is_in_cache(3,y=0)
1095
+ True
1096
+
1097
+ TESTS:
1098
+
1099
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1100
+ immutable unhashable objects which define
1101
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1102
+
1103
+ sage: # needs sage.rings.padics
1104
+ sage: @cached_function
1105
+ ....: def f(x): return x
1106
+ sage: K.<u> = Qq(4)
1107
+ sage: x = K(1,1); x
1108
+ 1 + O(2)
1109
+ sage: f.is_in_cache(x)
1110
+ False
1111
+ sage: f(x)
1112
+ 1 + O(2)
1113
+ sage: f.is_in_cache(x)
1114
+ True
1115
+ """
1116
+ k = self.get_key_args_kwds(args, kwds)
1117
+ try:
1118
+ return k in self.cache
1119
+ except TypeError: # k is not hashable
1120
+ k = dict_key(k)
1121
+ return k in self.cache
1122
+
1123
+ def set_cache(self, value, *args, **kwds):
1124
+ """
1125
+ Set the value for those args and keyword args
1126
+ Mind the unintuitive syntax (value first).
1127
+ Any idea on how to improve that welcome!
1128
+
1129
+ EXAMPLES::
1130
+
1131
+ sage: # needs sage.combinat sage.libs.flint
1132
+ sage: g = CachedFunction(number_of_partitions)
1133
+ sage: a = g(5)
1134
+ sage: g.cache
1135
+ {((5, 'default'), ()): 7}
1136
+ sage: g.set_cache(17, 5)
1137
+ sage: g.cache
1138
+ {((5, 'default'), ()): 17}
1139
+ sage: g(5)
1140
+ 17
1141
+
1142
+ TESTS:
1143
+
1144
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1145
+ immutable unhashable objects which define
1146
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1147
+
1148
+ sage: # needs sage.rings.padics
1149
+ sage: @cached_function
1150
+ ....: def f(x): return x
1151
+ sage: K.<u> = Qq(4)
1152
+ sage: x = K(1,1); x
1153
+ 1 + O(2)
1154
+ sage: f.set_cache(x, x)
1155
+ sage: f.is_in_cache(x)
1156
+ True
1157
+
1158
+ DEVELOPER NOTE:
1159
+
1160
+ Is there a way to use the following intuitive syntax?
1161
+
1162
+ ::
1163
+
1164
+ sage: g(5) = 19 # todo: not implemented
1165
+ sage: g(5) # todo: not implemented
1166
+ 19
1167
+ """
1168
+ k = self.get_key_args_kwds(args, kwds)
1169
+ try:
1170
+ self.cache[k] = value
1171
+ except TypeError: # k is not hashable
1172
+ k = dict_key(k)
1173
+ self.cache[k] = value
1174
+
1175
+ def get_key(self, *args, **kwds):
1176
+ """
1177
+ Return the key in the cache to be used when ``args``
1178
+ and ``kwds`` are passed in as parameters.
1179
+
1180
+ EXAMPLES::
1181
+
1182
+ sage: @cached_function
1183
+ ....: def foo(x):
1184
+ ....: return x^2
1185
+ sage: foo(2)
1186
+ 4
1187
+ sage: foo.get_key(2)
1188
+ ((2,), ())
1189
+ sage: foo.get_key(x=3)
1190
+ ((3,), ())
1191
+
1192
+ Examples for cached methods::
1193
+
1194
+ sage: class Foo:
1195
+ ....: def __init__(self, x):
1196
+ ....: self._x = x
1197
+ ....: @cached_method
1198
+ ....: def f(self, y, z=0):
1199
+ ....: return self._x * y + z
1200
+ sage: a = Foo(2)
1201
+ sage: z = a.f(37)
1202
+ sage: k = a.f.get_key(37); k
1203
+ ((37, 0), ())
1204
+ sage: a.f.cache[k] is z
1205
+ True
1206
+
1207
+ Note that the method does not test whether there are
1208
+ too many arguments, or wrong argument names::
1209
+
1210
+ sage: a.f.get_key(1,2,3,x=4,y=5,z=6)
1211
+ ((1, 2, 3), (('x', 4), ('y', 5), ('z', 6)))
1212
+
1213
+ It does, however, take into account the different
1214
+ ways of providing named arguments, possibly with a
1215
+ default value::
1216
+
1217
+ sage: a.f.get_key(5)
1218
+ ((5, 0), ())
1219
+ sage: a.f.get_key(y=5)
1220
+ ((5, 0), ())
1221
+ sage: a.f.get_key(5,0)
1222
+ ((5, 0), ())
1223
+ sage: a.f.get_key(5,z=0)
1224
+ ((5, 0), ())
1225
+ sage: a.f.get_key(y=5,z=0)
1226
+ ((5, 0), ())
1227
+ """
1228
+ return self.get_key_args_kwds(args, kwds)
1229
+
1230
+ def __repr__(self):
1231
+ """
1232
+ EXAMPLES::
1233
+
1234
+ sage: g = CachedFunction(number_of_partitions)
1235
+ sage: g # indirect doctest
1236
+ Cached version of <function number_of_partitions at 0x...>
1237
+ """
1238
+ try:
1239
+ return "Cached version of {}".format(self.f)
1240
+ except AttributeError:
1241
+ return "Cached version of a method (pending reassignment)"
1242
+
1243
+ def clear_cache(self):
1244
+ """
1245
+ Clear the cache dictionary.
1246
+
1247
+ EXAMPLES::
1248
+
1249
+ sage: # needs sage.combinat
1250
+ sage: g = CachedFunction(number_of_partitions)
1251
+ sage: a = g(5) # needs sage.libs.flint
1252
+ sage: g.cache # needs sage.libs.flint
1253
+ {((5, 'default'), ()): 7}
1254
+ sage: g.clear_cache()
1255
+ sage: g.cache
1256
+ {}
1257
+ """
1258
+ self.cache.clear()
1259
+
1260
+ def precompute(self, arglist, num_processes=1):
1261
+ """
1262
+ Cache values for a number of inputs. Do the computation
1263
+ in parallel, and only bother to compute values that we
1264
+ haven't already cached.
1265
+
1266
+ INPUT:
1267
+
1268
+ - ``arglist`` -- list (or iterables) of arguments for which
1269
+ the method shall be precomputed
1270
+
1271
+ - ``num_processes`` -- number of processes used by
1272
+ :func:`~sage.parallel.decorate.parallel`
1273
+
1274
+ EXAMPLES::
1275
+
1276
+ sage: @cached_function
1277
+ ....: def oddprime_factors(n):
1278
+ ....: l = [p for p,e in factor(n) if p != 2]
1279
+ ....: return len(l)
1280
+ sage: oddprime_factors.precompute(range(1,100), 4)
1281
+ sage: oddprime_factors.cache[(25,),()]
1282
+ 1
1283
+ """
1284
+ from sage.parallel.decorate import parallel, normalize_input
1285
+ P = parallel(num_processes)(self.f)
1286
+ cdef list arglist2 = []
1287
+ for a in arglist:
1288
+ ak = normalize_input(a)
1289
+ if self.get_key_args_kwds(ak[0], ak[1]) not in self.cache:
1290
+ arglist2.append(ak)
1291
+ for ((args, kwargs), val) in P(arglist2):
1292
+ self.set_cache(val, *args, **kwargs)
1293
+
1294
+
1295
+ cached_function = decorator_keywords(CachedFunction)
1296
+
1297
+
1298
+ cdef class WeakCachedFunction(CachedFunction):
1299
+ """
1300
+ A version of :class:`CachedFunction` using weak references on the
1301
+ values.
1302
+
1303
+ If ``f`` is a function, do either ``g = weak_cached_function(f)`` to make
1304
+ a cached version of ``f``, or put ``@weak_cached_function`` right before
1305
+ the definition of ``f`` (i.e., use Python decorators)::
1306
+
1307
+ @weak_cached_function
1308
+ def f(...):
1309
+ ...
1310
+
1311
+ As an exception meant to improve performance, the most recently
1312
+ computed values are strongly referenced. The number of strongly
1313
+ cached values can be controlled by the ``cache`` keyword.
1314
+
1315
+ EXAMPLES::
1316
+
1317
+ sage: from sage.misc.cachefunc import weak_cached_function
1318
+ sage: class A: pass
1319
+ sage: @weak_cached_function(cache=0)
1320
+ ....: def f():
1321
+ ....: print("doing a computation")
1322
+ ....: return A()
1323
+ sage: a = f()
1324
+ doing a computation
1325
+
1326
+ The result is cached::
1327
+
1328
+ sage: b = f()
1329
+ sage: a is b
1330
+ True
1331
+
1332
+ However, if there are no strong references left, the result is
1333
+ deleted, and thus a new computation takes place::
1334
+
1335
+ sage: del a
1336
+ sage: del b
1337
+ sage: a = f()
1338
+ doing a computation
1339
+
1340
+ Above, we used the ``cache=0`` keyword. With a larger value, the
1341
+ most recently computed values are cached anyway, even if they are
1342
+ not referenced::
1343
+
1344
+ sage: @weak_cached_function(cache=3)
1345
+ ....: def f(x):
1346
+ ....: print("doing a computation for x={}".format(x))
1347
+ ....: return A()
1348
+ sage: a = f(1); del a
1349
+ doing a computation for x=1
1350
+ sage: a = f(2), f(1); del a
1351
+ doing a computation for x=2
1352
+ sage: a = f(3), f(1); del a
1353
+ doing a computation for x=3
1354
+ sage: a = f(4), f(1); del a
1355
+ doing a computation for x=4
1356
+ doing a computation for x=1
1357
+ sage: a = f(5), f(1); del a
1358
+ doing a computation for x=5
1359
+
1360
+ The parameter ``key`` can be used to ignore parameters for
1361
+ caching. In this example we ignore the parameter ``algorithm``::
1362
+
1363
+ sage: @weak_cached_function(key=lambda x,algorithm: x)
1364
+ ....: def mod_ring(x, algorithm='default'):
1365
+ ....: return IntegerModRing(x)
1366
+ sage: mod_ring(1,algorithm='default') is mod_ring(1,algorithm='algorithm') is mod_ring(1) is mod_ring(1,'default')
1367
+ True
1368
+
1369
+ TESTS:
1370
+
1371
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1372
+ immutable unhashable objects which define
1373
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1374
+
1375
+ sage: # needs sage.rings.padics
1376
+ sage: from sage.misc.cachefunc import weak_cached_function
1377
+ sage: @weak_cached_function
1378
+ ....: def f(x): return x+x
1379
+ sage: K.<u> = Qq(4)
1380
+ sage: R.<t> = K[]
1381
+ sage: x = t + K(1,1); x
1382
+ (1 + O(2^20))*t + 1 + O(2)
1383
+ sage: y = t + K(1,2); y
1384
+ (1 + O(2^20))*t + 1 + O(2^2)
1385
+ sage: x == y
1386
+ True
1387
+ sage: f(x) is f(x)
1388
+ True
1389
+ sage: f(y) is not f(x)
1390
+ True
1391
+
1392
+ Examples and tests for ``is_in_cache``::
1393
+
1394
+ sage: from sage.misc.cachefunc import weak_cached_function
1395
+ sage: class A:
1396
+ ....: def __init__(self, x):
1397
+ ....: self.x = x
1398
+ sage: @weak_cached_function(cache=0)
1399
+ ....: def f(n):
1400
+ ....: return A(n)
1401
+ sage: a = f(5)
1402
+
1403
+ The key 5 is in the cache, as long as there is a strong
1404
+ reference to the corresponding value::
1405
+
1406
+ sage: f.is_in_cache(5)
1407
+ True
1408
+
1409
+ However, if there are no strong references left, the cached
1410
+ item is removed from the cache::
1411
+
1412
+ sage: del a
1413
+ sage: f.is_in_cache(5)
1414
+ False
1415
+
1416
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1417
+ immutable unhashable objects which define
1418
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1419
+
1420
+ sage: # needs sage.rings.padics
1421
+ sage: from sage.misc.cachefunc import weak_cached_function
1422
+ sage: @weak_cached_function
1423
+ ....: def f(x): return x
1424
+ sage: K.<u> = Qq(4)
1425
+ sage: R.<t> = K[]
1426
+ sage: f.is_in_cache(t)
1427
+ False
1428
+ sage: f(t)
1429
+ (1 + O(2^20))*t
1430
+ sage: f.is_in_cache(t)
1431
+ True
1432
+
1433
+ Examples and tests for ``set_cache``::
1434
+
1435
+ sage: from sage.misc.cachefunc import weak_cached_function
1436
+ sage: @weak_cached_function
1437
+ ....: def f(n):
1438
+ ....: raise RuntimeError
1439
+ sage: f.set_cache(ZZ, 5)
1440
+ sage: f(5)
1441
+ Integer Ring
1442
+
1443
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1444
+ immutable unhashable objects which define
1445
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1446
+
1447
+ sage: # needs sage.rings.padics
1448
+ sage: from sage.misc.cachefunc import weak_cached_function
1449
+ sage: @weak_cached_function
1450
+ ....: def f(x): return x
1451
+ sage: K.<u> = Qq(4)
1452
+ sage: R.<t> = K[]
1453
+ sage: f.set_cache(t,t)
1454
+ sage: f.is_in_cache(t)
1455
+ True
1456
+ """
1457
+ def __init__(self, f, *, classmethod=False, name: str | None = None, key=None, **kwds) -> None:
1458
+ """
1459
+ The inputs to the function must be hashable or they must define
1460
+ :meth:`sage.structure.sage_object.SageObject._cache_key`.
1461
+ The outputs to the function must be weakly referenceable.
1462
+
1463
+ TESTS::
1464
+
1465
+ sage: from sage.misc.cachefunc import weak_cached_function
1466
+ sage: class A: pass
1467
+ sage: @weak_cached_function
1468
+ ....: def f():
1469
+ ....: return A()
1470
+ sage: f
1471
+ Cached version of <function f at ...>
1472
+
1473
+ We demonstrate that pickling works, provided the uncached function
1474
+ is available::
1475
+
1476
+ sage: import __main__
1477
+ sage: __main__.f = f
1478
+ sage: loads(dumps(f))
1479
+ Cached version of <function f at ...>
1480
+ sage: str(f.cache)
1481
+ '<CachedWeakValueDictionary at 0x...>'
1482
+ """
1483
+ self._common_init(f, None, name=name, key=key)
1484
+ self.cache = CachedWeakValueDictionary(**kwds)
1485
+
1486
+
1487
+ weak_cached_function = decorator_keywords(WeakCachedFunction)
1488
+
1489
+
1490
+ class CachedMethodPickle():
1491
+ """
1492
+ This class helps to unpickle cached methods.
1493
+
1494
+ .. NOTE::
1495
+
1496
+ Since :issue:`8611`, a cached method is an attribute
1497
+ of the instance (provided that it has a ``__dict__``).
1498
+ Hence, when pickling the instance, it would be attempted
1499
+ to pickle that attribute as well, but this is a problem,
1500
+ since functions cannot be pickled, currently. Therefore,
1501
+ we replace the actual cached method by a place holder,
1502
+ that kills itself as soon as any attribute is requested.
1503
+ Then, the original cached attribute is reinstated. But the
1504
+ cached values are in fact saved (if ``do_pickle`` is set.)
1505
+
1506
+ EXAMPLES::
1507
+
1508
+ sage: R.<x, y, z> = PolynomialRing(QQ, 3)
1509
+ sage: I = R * (x^3 + y^3 + z^3, x^4 - y^4)
1510
+ sage: I.groebner_basis() # needs sage.libs.singular
1511
+ [y^5*z^3 - 1/4*x^2*z^6 + 1/2*x*y*z^6 + 1/4*y^2*z^6,
1512
+ x^2*y*z^3 - x*y^2*z^3 + 2*y^3*z^3 + z^6,
1513
+ x*y^3 + y^4 + x*z^3, x^3 + y^3 + z^3]
1514
+ sage: I.groebner_basis
1515
+ Cached version of <function ...groebner_basis at 0x...>
1516
+
1517
+ We now pickle and unpickle the ideal. The cached method
1518
+ ``groebner_basis`` is replaced by a placeholder::
1519
+
1520
+ sage: J = loads(dumps(I))
1521
+ sage: J.groebner_basis
1522
+ Pickle of the cached method "groebner_basis"
1523
+
1524
+ But as soon as any other attribute is requested from the
1525
+ placeholder, it replaces itself by the cached method, and
1526
+ the entries of the cache are actually preserved::
1527
+
1528
+ sage: J.groebner_basis.is_in_cache() # needs sage.libs.singular
1529
+ True
1530
+ sage: J.groebner_basis
1531
+ Cached version of <function ...groebner_basis at 0x...>
1532
+ sage: J.groebner_basis() == I.groebner_basis() # needs sage.libs.singular
1533
+ True
1534
+
1535
+ TESTS:
1536
+
1537
+ Since :issue:`11115`, there is a special implementation for
1538
+ cached methods that don't take arguments::
1539
+
1540
+ sage: class A:
1541
+ ....: @cached_method(do_pickle=True)
1542
+ ....: def f(self): return 1
1543
+ ....: @cached_method(do_pickle=True)
1544
+ ....: def g(self, x): return x
1545
+
1546
+ sage: import __main__
1547
+ sage: __main__.A = A
1548
+ sage: a = A()
1549
+ sage: type(a.f)
1550
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
1551
+ sage: type(a.g)
1552
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
1553
+
1554
+ We demonstrate that both implementations can be pickled and
1555
+ preserve the cache. For that purpose, we assign nonsense to the
1556
+ cache. Of course, it is a very bad idea to override the cache in
1557
+ that way. So, please don't try this at home::
1558
+
1559
+ sage: a.f.set_cache(0)
1560
+ sage: a.f()
1561
+ 0
1562
+ sage: a.g.set_cache(0,x=1)
1563
+ sage: a.g(1)
1564
+ 0
1565
+ sage: b = loads(dumps(a))
1566
+ sage: b.f()
1567
+ 0
1568
+ sage: b.g(1)
1569
+ 0
1570
+
1571
+ Anyway, the cache will be automatically reconstructed after
1572
+ clearing it::
1573
+
1574
+ sage: a.f.clear_cache()
1575
+ sage: a.f()
1576
+ 1
1577
+
1578
+ sage: a.g.clear_cache()
1579
+ sage: a.g(1)
1580
+ 1
1581
+
1582
+ AUTHOR:
1583
+
1584
+ - Simon King (2011-01)
1585
+ """
1586
+ def __init__(self, inst, name, cache=None) -> None:
1587
+ """
1588
+ INPUT:
1589
+
1590
+ - ``inst`` -- some instance
1591
+ - ``name`` -- string; usually the name of an attribute
1592
+ of ``inst`` to which ``self`` is assigned
1593
+
1594
+ TESTS::
1595
+
1596
+ sage: from sage.misc.cachefunc import CachedMethodPickle
1597
+ sage: P = CachedMethodPickle(1, 'foo')
1598
+ sage: P
1599
+ Pickle of the cached method "foo"
1600
+ """
1601
+ self._instance = inst
1602
+ self._name = name
1603
+ self._cache = cache
1604
+
1605
+ def __repr__(self):
1606
+ """
1607
+ TESTS::
1608
+
1609
+ sage: R.<x, y, z> = PolynomialRing(QQ, 3)
1610
+ sage: I = R * (x^3 + y^3 + z^3, x^4 - y^4)
1611
+ sage: G = I.groebner_basis() # needs sage.libs.singular
1612
+ sage: J = loads(dumps(I))
1613
+ sage: J.groebner_basis # indirect doctest
1614
+ Pickle of the cached method "groebner_basis"
1615
+ """
1616
+ return 'Pickle of the cached method "{}"'.format(self._name)
1617
+
1618
+ def __reduce__(self):
1619
+ """
1620
+ This class is a pickle. However, sometimes, pickles
1621
+ need to be pickled another time.
1622
+
1623
+ TESTS::
1624
+
1625
+ sage: R.<x, y, z> = PolynomialRing(QQ, 3)
1626
+ sage: I = R * (x^3 + y^3 + z^3, x^4 - y^4)
1627
+ sage: I.groebner_basis() # needs sage.libs.singular
1628
+ [y^5*z^3 - 1/4*x^2*z^6 + 1/2*x*y*z^6 + 1/4*y^2*z^6,
1629
+ x^2*y*z^3 - x*y^2*z^3 + 2*y^3*z^3 + z^6,
1630
+ x*y^3 + y^4 + x*z^3, x^3 + y^3 + z^3]
1631
+ sage: J = loads(dumps(I))
1632
+ sage: J.groebner_basis
1633
+ Pickle of the cached method "groebner_basis"
1634
+
1635
+ When we now pickle ``J``, the pickle of the cached method
1636
+ needs to be taken care of::
1637
+
1638
+ sage: K = loads(dumps(J)) # indirect doctest
1639
+ sage: K.groebner_basis
1640
+ Pickle of the cached method "groebner_basis"
1641
+ sage: K.groebner_basis.cache # needs sage.libs.singular
1642
+ {(('', None, None, False), ()):
1643
+ [y^5*z^3 - 1/4*x^2*z^6 + 1/2*x*y*z^6 + 1/4*y^2*z^6,
1644
+ x^2*y*z^3 - x*y^2*z^3 + 2*y^3*z^3 + z^6,
1645
+ x*y^3 + y^4 + x*z^3, x^3 + y^3 + z^3]}
1646
+ """
1647
+ return CachedMethodPickle, (self._instance, self._name, self._cache)
1648
+
1649
+ def __call__(self, *args, **kwds):
1650
+ """
1651
+ The purpose of this call method is to kill ``self`` and to
1652
+ replace it by an actual :class:`CachedMethodCaller`. The last
1653
+ thing that ``self`` does before disappearing is to call the
1654
+ :class:`CachedMethodCaller` and return the result.
1655
+
1656
+ EXAMPLES::
1657
+
1658
+ sage: P.<a,b,c,d> = QQ[]
1659
+ sage: I = P*[a,b]
1660
+ sage: I.gens
1661
+ Cached version of <function ...gens at 0x...>
1662
+ sage: J = loads(dumps(I))
1663
+ sage: J.gens
1664
+ Pickle of the cached method "gens"
1665
+ sage: J.gens() # indirect doctest
1666
+ [a, b]
1667
+ sage: J.gens
1668
+ Cached version of <function ...gens at 0x...>
1669
+ """
1670
+ self._instance.__dict__.__delitem__(self._name)
1671
+ CM = getattr(self._instance, self._name)
1672
+ if self._cache is not None:
1673
+ if isinstance(CM, CachedMethodCallerNoArgs):
1674
+ CM.cache = self._cache
1675
+ else:
1676
+ for k, v in self._cache.items():
1677
+ CM.cache[k] = v
1678
+ return CM(*args, **kwds)
1679
+
1680
+ def __getattr__(self, s):
1681
+ """
1682
+ TESTS::
1683
+
1684
+ sage: R.<x, y, z> = PolynomialRing(QQ, 3)
1685
+ sage: I = R * (x^3 + y^3 + z^3, x^4 - y^4)
1686
+ sage: G = I.groebner_basis() # needs sage.libs.singular
1687
+ sage: J = loads(dumps(I))
1688
+ sage: J.groebner_basis
1689
+ Pickle of the cached method "groebner_basis"
1690
+
1691
+ If an attribute of name ``s`` is requested (say,
1692
+ ``is_in_cache``), the attribute ``self._name`` of
1693
+ ``self._instance`` is deleted. Then, the attribute
1694
+ of name ``s`` of the attribute ``self._name`` of
1695
+ ``self._instance`` is requested. Since ``self._name``
1696
+ is a cached method defined for the class of
1697
+ ``self._instance``, retrieving the just-deleted
1698
+ attribute ``self._name`` succeeds.
1699
+
1700
+ In that way, the unpickling of the cached method is
1701
+ finally accomplished::
1702
+
1703
+ sage: J.groebner_basis.is_in_cache() # indirect doctest # needs sage.libs.singular
1704
+ True
1705
+ sage: J.groebner_basis
1706
+ Cached version of <function ...groebner_basis at 0x...>
1707
+ """
1708
+ self._instance.__dict__.__delitem__(self._name)
1709
+ CM = getattr(self._instance, self._name)
1710
+ if self._cache is not None:
1711
+ if isinstance(CM, CachedMethodCallerNoArgs):
1712
+ CM.cache = self._cache
1713
+ else:
1714
+ for k, v in self._cache.items():
1715
+ CM.cache[k] = v
1716
+ return getattr(CM, s)
1717
+
1718
+
1719
+ cdef class CachedMethodCaller(CachedFunction):
1720
+ """
1721
+ Utility class that is used by :class:`CachedMethod` to bind a
1722
+ cached method to an instance.
1723
+
1724
+ .. NOTE::
1725
+
1726
+ Since :issue:`11115`, there is a special implementation
1727
+ :class:`CachedMethodCallerNoArgs` for methods that do not take
1728
+ arguments.
1729
+
1730
+ EXAMPLES::
1731
+
1732
+ sage: class A:
1733
+ ....: @cached_method
1734
+ ....: def bar(self, x):
1735
+ ....: return x^2
1736
+ sage: a = A()
1737
+ sage: a.bar
1738
+ Cached version of <function ...bar at 0x...>
1739
+ sage: type(a.bar)
1740
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
1741
+ sage: a.bar(2) is a.bar(x=2)
1742
+ True
1743
+
1744
+ TESTS:
1745
+
1746
+ As of :issue:`15692` the contents of the cache are not pickled anymore::
1747
+
1748
+ sage: import __main__
1749
+ sage: __main__.A = A
1750
+ sage: len(a.bar.cache)
1751
+ 1
1752
+ sage: b = loads(dumps(a))
1753
+ sage: len(b.bar.cache)
1754
+ 0
1755
+
1756
+ The parameter ``do_pickle`` can be used to change this behaviour::
1757
+
1758
+ sage: class A:
1759
+ ....: @cached_method(do_pickle=True)
1760
+ ....: def bar(self, x):
1761
+ ....: return x^2
1762
+
1763
+ sage: __main__.A = A
1764
+ sage: a = A()
1765
+ sage: a.bar(2)
1766
+ 4
1767
+ sage: len(a.bar.cache)
1768
+ 1
1769
+ sage: b = loads(dumps(a))
1770
+ sage: len(b.bar.cache)
1771
+ 1
1772
+ """
1773
+ def __init__(self, CachedMethod cachedmethod, inst, *, cache=None, name: str | None = None, key=None, do_pickle: bool = False) -> None:
1774
+ """
1775
+ EXAMPLES::
1776
+
1777
+ sage: class Foo:
1778
+ ....: def __init__(self, x):
1779
+ ....: self._x = x
1780
+ ....: @cached_method
1781
+ ....: def f(self, *args):
1782
+ ....: return self._x^2
1783
+ sage: a = Foo(2)
1784
+ sage: a.f.cache
1785
+ {}
1786
+ sage: a.f()
1787
+ 4
1788
+ sage: a.f.cache
1789
+ {((), ()): 4}
1790
+ """
1791
+ # initialize CachedFunction. Since the cached method is actually bound
1792
+ # to an instance, it now makes sense to initialise the ArgumentFixer
1793
+ # and reuse it for all bound cached method callers of the unbound
1794
+ # cached method.
1795
+ if cachedmethod._cachedfunc._argument_fixer is None:
1796
+ cachedmethod._cachedfunc.argfix_init()
1797
+ self._common_init(cachedmethod._cachedfunc.f,
1798
+ cachedmethod._cachedfunc._argument_fixer,
1799
+ name=name,
1800
+ key=key,
1801
+ do_pickle=do_pickle)
1802
+ if cache is None:
1803
+ self.cache = NonpicklingDict() if do_pickle else {}
1804
+ else:
1805
+ self.cache = cache
1806
+ self._instance = inst
1807
+ self._cachedmethod = cachedmethod
1808
+
1809
+ def __reduce__(self):
1810
+ """
1811
+ The pickle of a :class:`CachedMethodCaller` unpickles
1812
+ to a :class:`CachedMethodPickle`, that is able to replace
1813
+ itself by a copy of the original :class:`CachedMethodCaller`.
1814
+
1815
+ TESTS::
1816
+
1817
+ sage: R.<x, y, z> = PolynomialRing(QQ, 3)
1818
+ sage: I = R * (x^3 + y^3 + z^3, x^4 - y^4)
1819
+ sage: G = I.groebner_basis() # needs sage.libs.singular
1820
+ sage: J = loads(dumps(I)) # indirect doctest
1821
+ sage: J.groebner_basis
1822
+ Pickle of the cached method "groebner_basis"
1823
+ sage: J.groebner_basis.is_in_cache() # needs sage.libs.singular
1824
+ True
1825
+ sage: J.groebner_basis
1826
+ Cached version of <function ...groebner_basis at 0x...>
1827
+ """
1828
+ if self.do_pickle:
1829
+ return CachedMethodPickle, (self._instance, self.__name__, self.cache)
1830
+ return CachedMethodPickle, (self._instance, self.__name__)
1831
+
1832
+ def _instance_call(self, *args, **kwds):
1833
+ """
1834
+ Call the cached method without using the cache.
1835
+
1836
+ EXAMPLES::
1837
+
1838
+ sage: P.<a,b,c,d> = QQ[]
1839
+ sage: I = P * [a, b]
1840
+ sage: I.groebner_basis() # needs sage.libs.singular
1841
+ [a, b]
1842
+ sage: I.groebner_basis._instance_call() is I.groebner_basis() # needs sage.libs.singular
1843
+ False
1844
+ sage: I.groebner_basis._instance_call() == I.groebner_basis() # needs sage.libs.singular
1845
+ True
1846
+
1847
+ ::
1848
+
1849
+ sage: class Foo():
1850
+ ....: def __init__(self, x):
1851
+ ....: self._x = x
1852
+ ....: @cached_method
1853
+ ....: def f(self, n=2):
1854
+ ....: return self._x^n
1855
+ sage: a = Foo(2)
1856
+ sage: a.f()
1857
+ 4
1858
+
1859
+ Usually, a cached method is indeed cached::
1860
+
1861
+ sage: a.f() is a.f()
1862
+ True
1863
+
1864
+ However, when it becomes necessary, one can call it without
1865
+ using the cache::
1866
+
1867
+ sage: a.f._instance_call() is a.f()
1868
+ False
1869
+ sage: a.f._instance_call() == a.f()
1870
+ True
1871
+ """
1872
+ return self.f(self._instance, *args, **kwds)
1873
+
1874
+ cdef fix_args_kwds(self, tuple args, dict kwds):
1875
+ r"""
1876
+ Normalize parameters to obtain a key for the cache.
1877
+
1878
+ TESTS::
1879
+
1880
+ sage: class A():
1881
+ ....: def _f_normalize(self, x, algorithm): return x
1882
+ ....: @cached_method(key=_f_normalize)
1883
+ ....: def f(self, x, algorithm='default'): return x
1884
+ sage: a = A()
1885
+ sage: a.f(1, algorithm='default') is a.f(1) is a.f(1, algorithm='algorithm')
1886
+ True
1887
+ """
1888
+ if self._argument_fixer is None:
1889
+ self.argfix_init()
1890
+
1891
+ k = self._argument_fixer.fix_to_pos_args_kwds(args, kwds)
1892
+ if self.key is None:
1893
+ return k
1894
+ else:
1895
+ return self.key(self._instance, *k[0], **dict(k[1]))
1896
+
1897
+ def __call__(self, *args, **kwds):
1898
+ """
1899
+ Call the cached method.
1900
+
1901
+ TESTS::
1902
+
1903
+ sage: from sage.misc.superseded import deprecated_function_alias
1904
+ sage: class Foo:
1905
+ ....: @cached_method
1906
+ ....: def f(self, x, y=1):
1907
+ ....: return x+y
1908
+ ....: g = deprecated_function_alias(57, f)
1909
+ sage: a = Foo()
1910
+ sage: a.f(1) #indirect doctest
1911
+ 2
1912
+
1913
+ The result is cached, taking into account
1914
+ the three ways of providing (named) arguments::
1915
+
1916
+ sage: a.f(5) is a.f(5,1)
1917
+ True
1918
+ sage: a.f(5) is a.f(5,y=1)
1919
+ True
1920
+ sage: a.f(5) is a.f(y=1,x=5)
1921
+ True
1922
+
1923
+ The method can be called as an unbound function using the same cache::
1924
+
1925
+ sage: a.f(5) is Foo.f(a, 5)
1926
+ True
1927
+ sage: a.f(5) is Foo.f(a,5,1)
1928
+ True
1929
+ sage: a.f(5) is Foo.f(a, 5,y=1)
1930
+ True
1931
+ sage: a.f(5) is Foo.f(a, y=1,x=5)
1932
+ True
1933
+
1934
+ Cached methods are compatible with
1935
+ :meth:`sage.misc.superseded.deprecated_function_alias`::
1936
+
1937
+ sage: a.g(5) is a.f(5)
1938
+ doctest:...: DeprecationWarning: g is deprecated. Please use f instead.
1939
+ See https://github.com/sagemath/sage/issues/57 for details.
1940
+ True
1941
+ sage: Foo.g(a, 5) is a.f(5)
1942
+ True
1943
+ sage: Foo.g(a, y=1,x=5) is a.f(5)
1944
+ True
1945
+
1946
+ We test that :issue:`5843` is fixed::
1947
+
1948
+ sage: class Foo:
1949
+ ....: def __init__(self, x):
1950
+ ....: self._x = x
1951
+ ....: @cached_method
1952
+ ....: def f(self, y):
1953
+ ....: return self._x
1954
+ sage: a = Foo(2)
1955
+ sage: b = Foo(3)
1956
+ sage: a.f(b.f)
1957
+ 2
1958
+
1959
+ Check that :issue:`16316` has been fixed, i.e., caching works for
1960
+ immutable unhashable objects which define
1961
+ :meth:`sage.structure.sage_object.SageObject._cache_key`::
1962
+
1963
+ sage: # needs sage.rings.padics
1964
+ sage: K.<u> = Qq(4)
1965
+ sage: class A():
1966
+ ....: @cached_method
1967
+ ....: def f(self, x): return x+x
1968
+ sage: a = A()
1969
+ sage: x = K(1,1); x
1970
+ 1 + O(2)
1971
+ sage: y = K(1,2); y
1972
+ 1 + O(2^2)
1973
+ sage: x == y
1974
+ True
1975
+ sage: a.f(x) is a.f(x)
1976
+ True
1977
+ sage: a.f(y) is not a.f(x)
1978
+ True
1979
+ """
1980
+ if self._instance is None:
1981
+ # unbound cached method such as ``Foo.f``
1982
+ instance = args[0]
1983
+ args = args[1:]
1984
+ return self._cachedmethod.__get__(instance)(*args, **kwds)
1985
+
1986
+ k = self.get_key_args_kwds(args, kwds)
1987
+
1988
+ cdef dict cache = <dict>self.cache
1989
+ try:
1990
+ try:
1991
+ return cache[k]
1992
+ except TypeError: # k is not hashable
1993
+ k = dict_key(k)
1994
+ return cache[k]
1995
+ except KeyError:
1996
+ w = self._instance_call(*args, **kwds)
1997
+ cache[k] = w
1998
+ return w
1999
+
2000
+ def cached(self, *args, **kwds):
2001
+ """
2002
+ Return the result from the cache if available. If the value is
2003
+ not cached, raise :exc:`KeyError`.
2004
+
2005
+ EXAMPLES::
2006
+
2007
+ sage: class CachedMethodTest():
2008
+ ....: @cached_method
2009
+ ....: def f(self, x):
2010
+ ....: return x
2011
+ sage: o = CachedMethodTest()
2012
+ sage: CachedMethodTest.f.cached(o, 5)
2013
+ Traceback (most recent call last):
2014
+ ...
2015
+ KeyError: ((5,), ())
2016
+ sage: o.f.cached(5)
2017
+ Traceback (most recent call last):
2018
+ ...
2019
+ KeyError: ((5,), ())
2020
+ sage: o.f(5)
2021
+ 5
2022
+ sage: CachedMethodTest.f.cached(o, 5)
2023
+ 5
2024
+ sage: o.f.cached(5)
2025
+ 5
2026
+ """
2027
+ if self._instance is None:
2028
+ # unbound cached method such as ``CachedMethodTest.f``
2029
+ instance = args[0]
2030
+ args = args[1:]
2031
+ return self._cachedmethod.__get__(instance).cached(*args, **kwds)
2032
+
2033
+ k = self.get_key_args_kwds(args, kwds)
2034
+
2035
+ try:
2036
+ return self.cache[k]
2037
+ except TypeError: # k is not hashable
2038
+ k = dict_key(k)
2039
+ return self.cache[k]
2040
+
2041
+ def __get__(self, inst, cls):
2042
+ r"""
2043
+ Get a :class:`CachedMethodCaller` bound to a specific
2044
+ instance of the class of the cached method.
2045
+
2046
+ NOTE:
2047
+
2048
+ :class:`CachedMethodCaller` has a separate ``__get__``
2049
+ since the categories framework creates and caches the
2050
+ return value of ``CachedMethod.__get__`` with
2051
+ ``inst==None``.
2052
+
2053
+ This getter attempts to assign a bound method as an
2054
+ attribute to the given instance. If this is not
2055
+ possible (for example, for some extension classes),
2056
+ it is attempted to find an attribute ``_cached_methods``,
2057
+ and store/retrieve the bound method there. In that
2058
+ way, cached methods can be implemented for extension
2059
+ classes deriving from :class:`~sage.structure.parent.Parent`
2060
+ and :class:`~sage.structure.element.Element`.
2061
+
2062
+ TESTS:
2063
+
2064
+ Due to the separate ``__get__`` method, it is possible
2065
+ to define a cached method in one class and use it as
2066
+ an attribute of another class. ::
2067
+
2068
+ sage: class Foo:
2069
+ ....: @cached_method
2070
+ ....: def f(self, y):
2071
+ ....: return y - 1
2072
+ sage: class Bar:
2073
+ ....: f = Foo.f
2074
+ sage: b1 = Bar()
2075
+ sage: b2 = Bar()
2076
+
2077
+ The :class:`CachedMethod` is replaced by an instance
2078
+ of :class:`CachedMethodCaller` that (by :issue:`8611`)
2079
+ is set as an attribute. Hence, we have::
2080
+
2081
+ sage: b1.f is b1.f
2082
+ True
2083
+
2084
+ Any instance of ``Bar`` gets its own instance of
2085
+ :class:`CachedMethodCaller`::
2086
+
2087
+ sage: b1.f is b2.f
2088
+ False
2089
+
2090
+ The method caller knows the instance that it belongs
2091
+ to::
2092
+
2093
+ sage: Foo.f._instance is None
2094
+ True
2095
+ sage: b1.f._instance is b1
2096
+ True
2097
+ sage: b2.f._instance is b2
2098
+ True
2099
+
2100
+ An extension class can inherit a cached method from the
2101
+ parent or element class of a category (:issue:`11115`).
2102
+ See :class:`CachedMethodCaller` for examples.
2103
+
2104
+ Verify that :issue:`16337` has been resolved::
2105
+
2106
+ sage: class Foo:
2107
+ ....: @cached_method(key=lambda self,y: y+1)
2108
+ ....: def f(self, y):
2109
+ ....: return y - 1
2110
+ sage: class Bar:
2111
+ ....: f = Foo.f
2112
+
2113
+ sage: b = Bar()
2114
+ sage: b.f(0)
2115
+ -1
2116
+ sage: b.f.cache
2117
+ {1: -1}
2118
+ """
2119
+ # This is for Parents or Elements that do not allow attribute assignment
2120
+ try:
2121
+ return (<dict>inst._cached_methods)[self._cachedmethod._cachedfunc.__name__]
2122
+ except (AttributeError, TypeError, KeyError):
2123
+ pass
2124
+
2125
+ cls = type(self)
2126
+ Caller = cls(self._cachedmethod, inst,
2127
+ cache=self._cachedmethod._get_instance_cache(inst),
2128
+ name=self._cachedmethod._cachedfunc.__name__,
2129
+ key=self.key, do_pickle=self.do_pickle)
2130
+
2131
+ try:
2132
+ setattr(inst, self._cachedmethod._cachedfunc.__name__, Caller)
2133
+ return Caller
2134
+ except AttributeError:
2135
+ pass
2136
+ try:
2137
+ if inst._cached_methods is None:
2138
+ inst._cached_methods = {self._cachedmethod._cachedfunc.__name__: Caller}
2139
+ else:
2140
+ (<dict>inst._cached_methods)[self._cachedmethod._cachedfunc.__name__] = Caller
2141
+ except AttributeError:
2142
+ pass
2143
+ return Caller
2144
+
2145
+ def precompute(self, arglist, num_processes=1):
2146
+ """
2147
+ Cache values for a number of inputs. Do the computation
2148
+ in parallel, and only bother to compute values that we
2149
+ haven't already cached.
2150
+
2151
+ INPUT:
2152
+
2153
+ - ``arglist`` -- list (or iterables) of arguments for which
2154
+ the method shall be precomputed
2155
+
2156
+ - ``num_processes`` -- number of processes used by
2157
+ :func:`~sage.parallel.decorate.parallel`
2158
+
2159
+ EXAMPLES::
2160
+
2161
+ sage: class Foo():
2162
+ ....: @cached_method
2163
+ ....: def f(self, i):
2164
+ ....: return i^2
2165
+ sage: foo = Foo()
2166
+ sage: foo.f(3)
2167
+ 9
2168
+ sage: foo.f(1)
2169
+ 1
2170
+ sage: foo.f.precompute(range(2), 2)
2171
+ sage: foo.f.cache == {((0,), ()): 0, ((1,), ()): 1, ((3,), ()): 9}
2172
+ True
2173
+ """
2174
+ from sage.parallel.decorate import parallel, normalize_input
2175
+ P = parallel(num_processes)(self._instance_call)
2176
+ cdef list arglist2 = []
2177
+ for a in arglist:
2178
+ ak = normalize_input(a)
2179
+ if self.get_key_args_kwds(ak[0], ak[1]) not in self.cache:
2180
+ arglist2.append(ak)
2181
+ for ((args, kwargs), val) in P(arglist2):
2182
+ self.set_cache(val, *args, **kwargs)
2183
+
2184
+
2185
+ cdef class CachedMethodCallerNoArgs(CachedFunction):
2186
+ """
2187
+ Utility class that is used by :class:`CachedMethod` to bind a
2188
+ cached method to an instance, in the case of a method that does
2189
+ not accept any arguments except ``self``.
2190
+
2191
+ .. NOTE::
2192
+
2193
+ The return value ``None`` would not be cached. So, if you have
2194
+ a method that does not accept arguments and may return ``None``
2195
+ after a lengthy computation, then ``@cached_method`` should not
2196
+ be used.
2197
+
2198
+ EXAMPLES::
2199
+
2200
+ sage: P.<a,b,c,d> = QQ[]
2201
+ sage: I = P*[a,b]
2202
+ sage: I.gens
2203
+ Cached version of <function ...gens at 0x...>
2204
+ sage: type(I.gens)
2205
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2206
+ sage: I.gens is I.gens
2207
+ True
2208
+ sage: I.gens() is I.gens()
2209
+ True
2210
+
2211
+ TESTS:
2212
+
2213
+ As of :issue:`15692` the contents of the cache are not pickled anymore::
2214
+
2215
+ sage: class A:
2216
+ ....: @cached_method
2217
+ ....: def bar(self):
2218
+ ....: return 4
2219
+ sage: import __main__
2220
+ sage: __main__.A = A
2221
+ sage: a = A()
2222
+ sage: a.bar()
2223
+ 4
2224
+ sage: a.bar.cache
2225
+ 4
2226
+ sage: b = loads(dumps(a))
2227
+ sage: b.bar.cache
2228
+
2229
+ The parameter ``do_pickle`` can be used to change this behaviour::
2230
+
2231
+ sage: class A:
2232
+ ....: @cached_method(do_pickle=True)
2233
+ ....: def bar(self):
2234
+ ....: return 4
2235
+
2236
+ sage: __main__.A = A
2237
+ sage: a = A()
2238
+ sage: a.bar()
2239
+ 4
2240
+ sage: a.bar.cache
2241
+ 4
2242
+ sage: b = loads(dumps(a))
2243
+ sage: b.bar.cache
2244
+ 4
2245
+
2246
+ AUTHOR:
2247
+
2248
+ - Simon King (2011-04)
2249
+ """
2250
+ def __init__(self, inst, f, cache=None, name: str | None = None, do_pickle: bool = False):
2251
+ """
2252
+ EXAMPLES::
2253
+
2254
+ sage: class Foo:
2255
+ ....: def __init__(self, x):
2256
+ ....: self._x = x
2257
+ ....: @cached_method
2258
+ ....: def f(self):
2259
+ ....: return self._x^2
2260
+ sage: a = Foo(2)
2261
+ sage: print(a.f.cache)
2262
+ None
2263
+ sage: a.f()
2264
+ 4
2265
+ sage: a.f.cache
2266
+ 4
2267
+ """
2268
+ # initialize CachedFunction
2269
+ if isinstance(f, str):
2270
+ try:
2271
+ F = getattr(inst.__class__, f)
2272
+ except AttributeError:
2273
+ F = getattr(inst, f)
2274
+ if isinstance(F, CachedFunction):
2275
+ f = F.f
2276
+ else:
2277
+ f = F
2278
+ self._common_init(f, None, name=name, do_pickle=do_pickle)
2279
+ # This is for unpickling a CachedMethodCallerNoArgs out
2280
+ # of an old CachedMethodCaller:
2281
+ cachename = '_cache__' + self.__name__
2282
+ if hasattr(inst, cachename):
2283
+ # This is for data that are pickled in an old format
2284
+ CACHE = getattr(inst, cachename)
2285
+ if len(CACHE) > 1:
2286
+ raise TypeError("Apparently you are opening a pickle in which '{}' was a method accepting arguments".format(name))
2287
+ if len(CACHE) == 1:
2288
+ self.cache = CACHE.values()[0]
2289
+ else:
2290
+ self.cache = cache
2291
+ delattr(inst, cachename)
2292
+ else:
2293
+ self.cache = cache # None means: the underlying method will be called
2294
+ self._instance = inst
2295
+
2296
+ def __reduce__(self):
2297
+ """
2298
+ Since functions cannot be pickled, the cached method caller
2299
+ is pickled by a :class:`CachedMethodPickle`, that replaces
2300
+ itself by an actual :class:`CachedMethodCallerNoArgs` as soon
2301
+ as it is asked to do anything.
2302
+
2303
+ TESTS::
2304
+
2305
+ sage: P.<a,b,c,d> = QQ[]
2306
+ sage: I = P*[a,b]
2307
+ sage: I.gens()
2308
+ [a, b]
2309
+ sage: I.gens
2310
+ Cached version of <function ...gens at 0x...>
2311
+ sage: J = loads(dumps(I))
2312
+ sage: J.gens
2313
+ Pickle of the cached method "gens"
2314
+ sage: J.gens.cache # the cache is dropped because gens is not marked with do_pickle=True
2315
+ sage: J.gens
2316
+ Cached version of <function ...gens at 0x...>
2317
+ """
2318
+ if self.do_pickle:
2319
+ return CachedMethodPickle, (self._instance, self.__name__, self.cache)
2320
+ return CachedMethodPickle, (self._instance, self.__name__)
2321
+
2322
+ def _instance_call(self):
2323
+ """
2324
+ Call the cached method without using the cache.
2325
+
2326
+ EXAMPLES::
2327
+
2328
+ sage: P.<a,b,c,d> = QQ[]
2329
+ sage: I = P*[a,b]
2330
+ sage: I.gens()
2331
+ [a, b]
2332
+ sage: I.gens._instance_call() is I.gens()
2333
+ False
2334
+ sage: I.gens._instance_call() == I.gens()
2335
+ True
2336
+ """
2337
+ return self.f(self._instance)
2338
+
2339
+ def __call__(self):
2340
+ """
2341
+ Call the cached method.
2342
+
2343
+ EXAMPLES::
2344
+
2345
+ sage: P.<a,b,c,d> = QQ[]
2346
+ sage: I = P*[a,b]
2347
+ sage: I.gens() # indirect doctest
2348
+ [a, b]
2349
+ sage: I.gens() is I.gens()
2350
+ True
2351
+ """
2352
+ if self.cache is None:
2353
+ f = self.f
2354
+ self.cache = f(self._instance)
2355
+ return self.cache
2356
+
2357
+ def set_cache(self, value):
2358
+ """
2359
+ Override the cache with a specific value.
2360
+
2361
+ .. NOTE::
2362
+
2363
+ ``None`` is not suitable for a cached value. It would be
2364
+ interpreted as an empty cache, forcing a new computation.
2365
+
2366
+ EXAMPLES::
2367
+
2368
+ sage: P.<a,b,c,d> = QQ[]
2369
+ sage: I = P*[a,b]
2370
+ sage: I.gens()
2371
+ [a, b]
2372
+ sage: I.gens.set_cache('bar')
2373
+ sage: I.gens()
2374
+ 'bar'
2375
+
2376
+ The cache can be emptied and thus the original value will
2377
+ be reconstructed::
2378
+
2379
+ sage: I.gens.clear_cache()
2380
+ sage: I.gens()
2381
+ [a, b]
2382
+
2383
+ The attempt to assign ``None`` to the cache fails::
2384
+
2385
+ sage: I.gens.set_cache(None)
2386
+ sage: I.gens()
2387
+ [a, b]
2388
+ """
2389
+ self.cache = value
2390
+
2391
+ def clear_cache(self):
2392
+ r"""
2393
+ Clear the cache dictionary.
2394
+
2395
+ EXAMPLES::
2396
+
2397
+ sage: P.<a,b,c,d> = QQ[]
2398
+ sage: I = P*[a,b]
2399
+ sage: I.gens()
2400
+ [a, b]
2401
+ sage: I.gens.set_cache('bar')
2402
+ sage: I.gens()
2403
+ 'bar'
2404
+
2405
+ The cache can be emptied and thus the original value will
2406
+ be reconstructed::
2407
+
2408
+ sage: I.gens.clear_cache()
2409
+ sage: I.gens()
2410
+ [a, b]
2411
+ """
2412
+ self.cache = None
2413
+
2414
+ def is_in_cache(self):
2415
+ """
2416
+ Answers whether the return value is already in the cache.
2417
+
2418
+ .. NOTE::
2419
+
2420
+ Recall that a cached method without arguments cannot cache
2421
+ the return value ``None``.
2422
+
2423
+ EXAMPLES::
2424
+
2425
+ sage: P.<x,y> = QQ[]
2426
+ sage: I = P*[x,y]
2427
+ sage: I.gens.is_in_cache()
2428
+ False
2429
+ sage: I.gens()
2430
+ [x, y]
2431
+ sage: I.gens.is_in_cache()
2432
+ True
2433
+ """
2434
+ return self.cache is not None
2435
+
2436
+ def __get__(self, inst, cls):
2437
+ """
2438
+ Get a :class:`CachedMethodCallerNoArgs` bound to a specific
2439
+ instance of the class of the cached method.
2440
+
2441
+ NOTE:
2442
+
2443
+ :class:`CachedMethodCallerNoArgs` has a separate ``__get__``
2444
+ since the categories framework creates and caches the
2445
+ return value of ``CachedMethod.__get__`` with
2446
+ ``inst==None``.
2447
+
2448
+ This getter attempts to assign a bound method as an
2449
+ attribute to the given instance. If this is not
2450
+ possible (for example, for some extension classes),
2451
+ it is attempted to find an attribute ``_cached_methods``,
2452
+ and store/retrieve the bound method there. In that
2453
+ way, cached methods can be implemented for extension
2454
+ classes deriving from :class:`~sage.structure.parent.Parent`
2455
+ and :class:`~sage.structure.element.Element`.
2456
+
2457
+ TESTS:
2458
+
2459
+ Due to the separate ``__get__`` method, it is possible
2460
+ to define a cached method in one class and use it as
2461
+ an attribute of another class. ::
2462
+
2463
+ sage: class Foo:
2464
+ ....: def __init__(self, n):
2465
+ ....: self.__n = n
2466
+ ....: @cached_method
2467
+ ....: def f(self):
2468
+ ....: return self.__n^2
2469
+ sage: class Bar:
2470
+ ....: f = Foo.f
2471
+ sage: b1 = Bar()
2472
+ sage: b2 = Bar()
2473
+
2474
+ The :class:`CachedMethod` is replaced by an instance of
2475
+ :class:`CachedMethodCallerNoArgs` that is set as an
2476
+ attribute. Hence, we have::
2477
+
2478
+ sage: b1.f is b1.f
2479
+ True
2480
+ sage: type(b1.f)
2481
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2482
+
2483
+ Any instance of ``Bar`` gets its own instance of
2484
+ :class:`CachedMethodCaller`::
2485
+
2486
+ sage: b1.f is b2.f
2487
+ False
2488
+
2489
+ The method caller knows the instance that it belongs
2490
+ to::
2491
+
2492
+ sage: Foo.f._instance is None
2493
+ True
2494
+ sage: b1.f._instance is b1
2495
+ True
2496
+ sage: b2.f._instance is b2
2497
+ True
2498
+ """
2499
+ # This is for Parents or Elements that do not allow attribute assignment
2500
+ try:
2501
+ return (<dict>inst._cached_methods)[self.__name__]
2502
+ except (AttributeError, TypeError, KeyError):
2503
+ pass
2504
+ Caller = CachedMethodCallerNoArgs(inst, self.f, name=self.__name__, do_pickle=self.do_pickle)
2505
+ try:
2506
+ setattr(inst, self.__name__, Caller)
2507
+ return Caller
2508
+ except AttributeError:
2509
+ pass
2510
+ try:
2511
+ if inst._cached_methods is None:
2512
+ inst._cached_methods = {self.__name__: Caller}
2513
+ else:
2514
+ (<dict>inst._cached_methods)[self.__name__] = Caller
2515
+ except AttributeError:
2516
+ pass
2517
+ return Caller
2518
+
2519
+
2520
+ cdef class GloballyCachedMethodCaller(CachedMethodCaller):
2521
+ """
2522
+ Implementation of cached methods in case that the cache is not
2523
+ stored in the instance, but in some global object. In particular,
2524
+ it is used to implement :class:`CachedInParentMethod`.
2525
+
2526
+ The only difference is that the instance is used as part of the
2527
+ key.
2528
+ """
2529
+ cdef get_key_args_kwds(self, tuple args, dict kwds):
2530
+ """
2531
+ Return the key in the cache to be used when ``args`` and
2532
+ ``kwds`` are passed in as parameters.
2533
+
2534
+ EXAMPLES::
2535
+
2536
+ sage: class MyParent(Parent):
2537
+ ....: pass
2538
+ sage: class MyElement(): # indirect doctest
2539
+ ....: def __init__(self, x):
2540
+ ....: self.x = x
2541
+ ....: def parent(self):
2542
+ ....: return MyParent()
2543
+ ....: @cached_in_parent_method
2544
+ ....: def f(self):
2545
+ ....: return self.x^2
2546
+ sage: a = MyElement(2)
2547
+ sage: a.f.get_key()
2548
+ (<__main__.MyElement object at 0x...>, ((), ()))
2549
+ sage: a.f.get_key()[0] is a
2550
+ True
2551
+ """
2552
+ k = self.fix_args_kwds(args, kwds)
2553
+ return (self._instance, k)
2554
+
2555
+
2556
+ cdef class CachedMethod():
2557
+ """
2558
+ A decorator that creates a cached version of an instance
2559
+ method of a class.
2560
+
2561
+ .. NOTE::
2562
+
2563
+ For proper behavior, the method must be a pure function (no side
2564
+ effects). Arguments to the method must be hashable or transformed into
2565
+ something hashable using ``key`` or they must define
2566
+ :meth:`sage.structure.sage_object.SageObject._cache_key`.
2567
+
2568
+ EXAMPLES::
2569
+
2570
+ sage: class Foo():
2571
+ ....: @cached_method
2572
+ ....: def f(self, t, x=2):
2573
+ ....: print('computing')
2574
+ ....: return t**x
2575
+ sage: a = Foo()
2576
+
2577
+ The example shows that the actual computation
2578
+ takes place only once, and that the result is
2579
+ identical for equivalent input::
2580
+
2581
+ sage: res = a.f(3, 2); res
2582
+ computing
2583
+ 9
2584
+ sage: a.f(t = 3, x = 2) is res
2585
+ True
2586
+ sage: a.f(3) is res
2587
+ True
2588
+
2589
+ Note, however, that accessing the attribute directly will call :meth:`__get__`,
2590
+ and returns a :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`.
2591
+
2592
+ ::
2593
+
2594
+ sage: P.<a,b,c,d> = QQ[]
2595
+ sage: I = P*[a,b]
2596
+ sage: type(I.__class__.gens)
2597
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2598
+ sage: type(I.__class__.__dict__["gens"])
2599
+ <class 'sage.misc.cachefunc.CachedMethod'>
2600
+
2601
+ The parameter ``key`` can be used to pass a function which creates a
2602
+ custom cache key for inputs. In the following example, this parameter is
2603
+ used to ignore the ``algorithm`` keyword for caching::
2604
+
2605
+ sage: class A():
2606
+ ....: def _f_normalize(self, x, algorithm): return x
2607
+ ....: @cached_method(key=_f_normalize)
2608
+ ....: def f(self, x, algorithm='default'): return x
2609
+ sage: a = A()
2610
+ sage: a.f(1, algorithm='default') is a.f(1) is a.f(1, algorithm='algorithm')
2611
+ True
2612
+
2613
+ The parameter ``do_pickle`` can be used to enable pickling of the cache.
2614
+ Usually the cache is not stored when pickling::
2615
+
2616
+ sage: class A():
2617
+ ....: @cached_method
2618
+ ....: def f(self, x): return None
2619
+ sage: import __main__
2620
+ sage: __main__.A = A
2621
+ sage: a = A()
2622
+ sage: a.f(1)
2623
+ sage: len(a.f.cache)
2624
+ 1
2625
+ sage: b = loads(dumps(a))
2626
+ sage: len(b.f.cache)
2627
+ 0
2628
+
2629
+ When ``do_pickle`` is set, the pickle contains the contents of the cache::
2630
+
2631
+ sage: class A():
2632
+ ....: @cached_method(do_pickle=True)
2633
+ ....: def f(self, x): return None
2634
+ sage: __main__.A = A
2635
+ sage: a = A()
2636
+ sage: a.f(1)
2637
+ sage: len(a.f.cache)
2638
+ 1
2639
+ sage: b = loads(dumps(a))
2640
+ sage: len(b.f.cache)
2641
+ 1
2642
+
2643
+ Cached methods cannot be copied like usual methods, see :issue:`12603`.
2644
+ Copying them can lead to very surprising results::
2645
+
2646
+ sage: class A:
2647
+ ....: @cached_method
2648
+ ....: def f(self):
2649
+ ....: return 1
2650
+ sage: class B:
2651
+ ....: g=A.f
2652
+ ....: def f(self):
2653
+ ....: return 2
2654
+
2655
+ sage: b=B()
2656
+ sage: b.f()
2657
+ 2
2658
+ sage: b.g()
2659
+ 1
2660
+ sage: b.f()
2661
+ 1
2662
+ """
2663
+ def __init__(self, f, name: str | None = None, key=None, do_pickle: bool = False) -> None:
2664
+ """
2665
+ EXAMPLES::
2666
+
2667
+ sage: class Foo():
2668
+ ....: def __init__(self, x):
2669
+ ....: self._x = x
2670
+ ....: @cached_method
2671
+ ....: def f(self, n):
2672
+ ....: return self._x^n
2673
+ ....: @cached_method
2674
+ ....: def f0(self):
2675
+ ....: return self._x^2
2676
+ sage: a = Foo(2)
2677
+ sage: a.f(2)
2678
+ 4
2679
+ sage: a.f0()
2680
+ 4
2681
+
2682
+ For methods with parameters, the results of method ``f`` is attempted
2683
+ to be stored in a dictionary attribute of the instance ``a``::
2684
+
2685
+ sage: hasattr(a, '_cache__f')
2686
+ True
2687
+ sage: a._cache__f
2688
+ {((2,), ()): 4}
2689
+ sage: a._cache_f0
2690
+ Traceback (most recent call last):
2691
+ ...
2692
+ AttributeError: 'Foo' object has no attribute '_cache_f0'...
2693
+
2694
+ As a shortcut, useful to speed up internal computations,
2695
+ the same dictionary is also available as an attribute
2696
+ of the ``CachedMethodCaller``::
2697
+
2698
+ sage: type(a.f)
2699
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
2700
+ sage: a.f.cache is a._cache__f
2701
+ True
2702
+
2703
+ Note that if the instance ``a`` would not accept attribute
2704
+ assignment, the computations would still be cached in
2705
+ ``a.f.cache``, and they would in fact be preserved when
2706
+ pickling.
2707
+
2708
+ The cached method ``f0`` accepts no arguments, which allows
2709
+ for an improved way of caching: By an attribute of the cached
2710
+ method itself. This cache is *only* available in that way, i.e.,
2711
+ it is not additionally stored as an attribute of ``a``::
2712
+
2713
+ sage: type(a.f0)
2714
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2715
+ sage: a.f0.cache
2716
+ 4
2717
+ sage: sorted(n for n in dir(a) if not n.startswith('__'))
2718
+ ['_cache__f', '_x', 'f', 'f0']
2719
+
2720
+ The cached method has its name and module set::
2721
+
2722
+ sage: f = Foo.__dict__["f"]
2723
+ sage: f.__name__
2724
+ 'f'
2725
+ sage: f.__module__
2726
+ '__main__'
2727
+ """
2728
+ self._cache_name = '_cache__' + (name or f.__name__)
2729
+ self._cachedfunc = CachedFunction(f, classmethod=True, name=name, key=key, do_pickle=do_pickle)
2730
+ self.__name__ = self._cachedfunc.__name__
2731
+ self.__cached_module__ = self._cachedfunc.__module__
2732
+
2733
+ @property
2734
+ def __module__(self) -> str:
2735
+ return self.__cached_module__
2736
+
2737
+ def __call__(self, inst, *args, **kwds):
2738
+ """
2739
+ Call the cached method as a function on an instance.
2740
+ This code path is not used directly except in a few rare cases,
2741
+ see examples for details.
2742
+
2743
+ INPUT:
2744
+
2745
+ - ``inst`` -- an instance on which the method is to be called
2746
+ - further positional or named arguments
2747
+
2748
+ EXAMPLES::
2749
+
2750
+
2751
+ sage: from sage.misc.superseded import deprecated_function_alias
2752
+ sage: class Foo():
2753
+ ....: def __init__(self, x):
2754
+ ....: self._x = x
2755
+ ....: @cached_method
2756
+ ....: def f(self, n=2):
2757
+ ....: return self._x^n
2758
+ ....: g = deprecated_function_alias(57, f)
2759
+ sage: a = Foo(2)
2760
+ sage: Foo.__dict__['f'](a)
2761
+ 4
2762
+
2763
+ This uses the cache as usual::
2764
+
2765
+ sage: Foo.__dict__['f'](a) is a.f()
2766
+ True
2767
+
2768
+ This feature makes cached methods compatible with
2769
+ :meth:`sage.misc.superseded.deprecated_function_alias`::
2770
+
2771
+ sage: a.g() is a.f()
2772
+ doctest:...: DeprecationWarning: g is deprecated. Please use f instead.
2773
+ See https://github.com/sagemath/sage/issues/57 for details.
2774
+ True
2775
+ sage: Foo.g(a) is a.f()
2776
+ True
2777
+ """
2778
+ return self.__get__(inst)(*args, **kwds)
2779
+
2780
+ cpdef _get_instance_cache(self, inst):
2781
+ """
2782
+ Return the cache dictionary.
2783
+
2784
+ TESTS::
2785
+
2786
+ sage: class Foo:
2787
+ ....: def __init__(self, x):
2788
+ ....: self._x = x
2789
+ ....: @cached_method
2790
+ ....: def f(self, n=2):
2791
+ ....: return self._x^n
2792
+ sage: a = Foo(2)
2793
+
2794
+ Initially ``_cache__f`` is not an attribute of ``a``::
2795
+
2796
+ sage: hasattr(a, "_cache__f")
2797
+ False
2798
+
2799
+ When the attribute is accessed (thus ``__get__`` is called),
2800
+ the cache is created and assigned to the attribute::
2801
+
2802
+ sage: a.f
2803
+ Cached version of <function Foo.f at 0x...>
2804
+ sage: a._cache__f
2805
+ {}
2806
+ sage: a.f()
2807
+ 4
2808
+ sage: a.f.cache
2809
+ {((2,), ()): 4}
2810
+ sage: a._cache__f
2811
+ {((2,), ()): 4}
2812
+
2813
+ Testing the method directly::
2814
+
2815
+ sage: a = Foo(2)
2816
+ sage: hasattr(a, "_cache__f")
2817
+ False
2818
+ sage: Foo.__dict__["f"]._get_instance_cache(a)
2819
+ {}
2820
+ sage: a._cache__f
2821
+ {}
2822
+ sage: a.f()
2823
+ 4
2824
+ sage: Foo.__dict__["f"]._get_instance_cache(a)
2825
+ {((2,), ()): 4}
2826
+
2827
+ Using ``__dict__`` is needed to access this function because
2828
+ ``Foo.f`` would call ``__get__`` and thus create a
2829
+ :class:`CachedMethodCaller`::
2830
+
2831
+ sage: type(Foo.f)
2832
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
2833
+ sage: type(Foo.__dict__["f"])
2834
+ <class 'sage.misc.cachefunc.CachedMethod'>
2835
+ """
2836
+ default = {} if self._cachedfunc.do_pickle else NonpicklingDict()
2837
+ try:
2838
+ return inst.__dict__.setdefault(self._cache_name, default)
2839
+ except AttributeError:
2840
+ return default
2841
+
2842
+ def __get__(self, object inst, cls):
2843
+ """
2844
+ Get a CachedMethodCaller bound to this specific instance of
2845
+ the class of the cached method.
2846
+
2847
+ TESTS::
2848
+
2849
+ sage: class Foo:
2850
+ ....: @cached_method
2851
+ ....: def f(self):
2852
+ ....: return 1
2853
+ ....: @cached_method
2854
+ ....: def g(self, x, n=3):
2855
+ ....: return x^n
2856
+ sage: a = Foo()
2857
+ sage: type(a.f)
2858
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2859
+ sage: type(a.g)
2860
+ <class 'sage.misc.cachefunc.CachedMethodCaller'>
2861
+
2862
+ By :issue:`8611`, it is attempted to set the
2863
+ CachedMethodCaller as an attribute of the instance ``a``,
2864
+ replacing the original cached attribute. Therefore, the
2865
+ ``__get__`` method will be used only once, which saves much
2866
+ time. Hence, we have::
2867
+
2868
+ sage: a.f is a.f
2869
+ True
2870
+ sage: a.g is a.g
2871
+ True
2872
+
2873
+ Verify that :issue:`16337` has been resolved::
2874
+
2875
+ sage: class Foo:
2876
+ ....: @cached_method(key=lambda self, x:x+1)
2877
+ ....: def f(self, x=0):
2878
+ ....: return x
2879
+
2880
+ sage: a = Foo()
2881
+ sage: a.f(0)
2882
+ 0
2883
+ sage: a.f.cache
2884
+ {1: 0}
2885
+ """
2886
+ # This is for Parents or Elements that do not allow attribute assignment:
2887
+ cdef str name
2888
+ try:
2889
+ name = self._cachedfunc.__name__
2890
+ except AttributeError:
2891
+ name = self.__name__
2892
+ try:
2893
+ return (<dict>inst._cached_methods)[name]
2894
+ except (AttributeError, TypeError, KeyError):
2895
+ pass
2896
+ # Apparently we need to construct the caller.
2897
+ # Since we have an optimized version for functions that do not accept arguments,
2898
+ # we need to analyse the argspec
2899
+ f = self._cachedfunc.f
2900
+ if self.nargs == 0:
2901
+ if isinstance(f, object) and not isfunction(f):
2902
+ try:
2903
+ if METH_NOARGS&PyCFunction_GetFlags(f.__get__(inst, cls)):
2904
+ self.nargs = 1
2905
+ except Exception:
2906
+ pass
2907
+ if self.nargs == 0:
2908
+ args, varargs, keywords, defaults, kwonlyargs, kwonlydefaults, annotations = sage_getargspec(f)
2909
+ if varargs is None and keywords is None and len(args)<=1:
2910
+ self.nargs = 1
2911
+ else:
2912
+ self.nargs = 2 # don't need the exact number
2913
+ if self.nargs == 1:
2914
+ Caller = CachedMethodCallerNoArgs(inst, f, name=name, do_pickle=self._cachedfunc.do_pickle)
2915
+ else:
2916
+ Caller = CachedMethodCaller(self, inst,
2917
+ cache=self._get_instance_cache(inst),
2918
+ name=name,
2919
+ key=self._cachedfunc.key,
2920
+ do_pickle=self._cachedfunc.do_pickle)
2921
+ try:
2922
+ setattr(inst, name, Caller)
2923
+ return Caller
2924
+ except AttributeError:
2925
+ pass
2926
+ try:
2927
+ if inst._cached_methods is None:
2928
+ inst._cached_methods = {name: Caller}
2929
+ else:
2930
+ (<dict>inst._cached_methods)[name] = Caller
2931
+ except AttributeError:
2932
+ pass
2933
+ return Caller
2934
+
2935
+
2936
+ cdef class CachedSpecialMethod(CachedMethod):
2937
+ """
2938
+ Cached version of *special* python methods.
2939
+
2940
+ IMPLEMENTATION:
2941
+
2942
+ For new style classes ``C``, it is not possible to override a special
2943
+ method, such as ``__hash__``, in the ``__dict__`` of an instance ``c`` of
2944
+ ``C``, because Python will always use what is provided by the class, not
2945
+ by the instance to avoid metaclass confusion. See
2946
+ `<https://docs.python.org/3/reference/datamodel.html#special-method-lookup>`_.
2947
+
2948
+ By consequence, if ``__hash__`` would be wrapped by using
2949
+ :class:`CachedMethod`, then ``hash(c)`` will access ``C.__hash__`` and bind
2950
+ it to ``c``, which means that the ``__get__`` method of
2951
+ :class:`CachedMethod` will be called. But there, we assume that Python has
2952
+ already inspected ``__dict__``, and thus a :class:`CachedMethodCaller`
2953
+ will be created over and over again.
2954
+
2955
+ Here, the ``__get__`` method will explicitly access the ``__dict__``, so that
2956
+ ``hash(c)`` will rely on a single :class:`CachedMethodCaller` stored in
2957
+ the ``__dict__``.
2958
+
2959
+ EXAMPLES::
2960
+
2961
+ sage: class C:
2962
+ ....: @cached_method
2963
+ ....: def __hash__(self):
2964
+ ....: print("compute hash")
2965
+ ....: return int(5)
2966
+ ....:
2967
+ sage: c = C()
2968
+ sage: type(C.__hash__)
2969
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2970
+
2971
+ The hash is computed only once, subsequent calls will use the value from
2972
+ the cache. This was implemented in :issue:`12601`.
2973
+
2974
+ ::
2975
+
2976
+ sage: hash(c) # indirect doctest
2977
+ compute hash
2978
+ 5
2979
+ sage: hash(c)
2980
+ 5
2981
+ """
2982
+ def __get__(self, object inst, cls):
2983
+ """
2984
+ Bind a :class:`CachedMethodCaller` to a specific instance, using ``__dict__``.
2985
+
2986
+ EXAMPLES::
2987
+
2988
+ sage: class C:
2989
+ ....: @cached_method
2990
+ ....: def __hash__(self):
2991
+ ....: print("compute hash")
2992
+ ....: return int(5)
2993
+ sage: c = C()
2994
+ sage: type(C.__hash__)
2995
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2996
+ sage: hash(c) # indirect doctest
2997
+ compute hash
2998
+ 5
2999
+ sage: hash(c)
3000
+ 5
3001
+
3002
+ Verify that :issue:`16337` has been resolved::
3003
+
3004
+ sage: class Foo:
3005
+ ....: @cached_method(key=lambda self, x:x+1)
3006
+ ....: def __hash__(self, x=0):
3007
+ ....: return x
3008
+
3009
+ sage: a = Foo()
3010
+ sage: a.__hash__(0)
3011
+ 0
3012
+ sage: a.__hash__.cache
3013
+ {1: 0}
3014
+ """
3015
+ # This is for Parents or Elements that do not allow attribute assignment:
3016
+ cdef str name
3017
+ try:
3018
+ name = self._cachedfunc.__name__
3019
+ except AttributeError:
3020
+ name = self.__name__
3021
+ cdef dict D = None
3022
+ if inst is not None:
3023
+ try:
3024
+ D = inst.__dict__
3025
+ except (TypeError, AttributeError):
3026
+ try:
3027
+ D = inst._cached_methods
3028
+ except (TypeError, AttributeError):
3029
+ raise TypeError("For a cached special method, either attribute assignment or a public '_cached_methods' attribute of type <dict> is needed")
3030
+ if D is None:
3031
+ # This can only happen in the case of _cached_methods
3032
+ D = inst._cached_methods = {}
3033
+ else:
3034
+ try:
3035
+ return D[name]
3036
+ except KeyError:
3037
+ pass
3038
+ # Apparently we need to construct the caller.
3039
+ # Since we have an optimized version for functions that do not accept arguments,
3040
+ # we need to analyse the argspec
3041
+ f = self._cachedfunc.f
3042
+ if self.nargs == 0:
3043
+ args, varargs, keywords, defaults, kwonlyargs, kwonlydefaults, annotations = sage_getargspec(f)
3044
+ if varargs is None and keywords is None and len(args)<=1:
3045
+ self.nargs = 1
3046
+ Caller = CachedMethodCallerNoArgs(inst, f, name=name, do_pickle=self._cachedfunc.do_pickle)
3047
+ else:
3048
+ self.nargs = 2 # don't need the exact number
3049
+ Caller = CachedMethodCaller(self, inst,
3050
+ cache=self._get_instance_cache(inst),
3051
+ name=name,
3052
+ key=self._cachedfunc.key,
3053
+ do_pickle=self._cachedfunc.do_pickle)
3054
+ elif self.nargs == 1:
3055
+ Caller = CachedMethodCallerNoArgs(inst, f, name=name, do_pickle=self._cachedfunc.do_pickle)
3056
+ else:
3057
+ Caller = CachedMethodCaller(self, inst,
3058
+ cache=self._get_instance_cache(inst),
3059
+ name=name,
3060
+ key=self._cachedfunc.key,
3061
+ do_pickle=self._cachedfunc.do_pickle)
3062
+ if inst is not None:
3063
+ try:
3064
+ setattr(inst, name, Caller)
3065
+ return Caller
3066
+ except AttributeError:
3067
+ pass
3068
+ D[name] = Caller
3069
+ return Caller
3070
+
3071
+ @decorator_keywords
3072
+ def cached_method(f, name: str | None = None, key=None, do_pickle: bool = False) -> CachedMethod:
3073
+ """
3074
+ A decorator for cached methods.
3075
+
3076
+ EXAMPLES:
3077
+
3078
+ In the following examples, one can see how a cached method works
3079
+ in application. Below, we demonstrate what is done behind the scenes::
3080
+
3081
+ sage: class C:
3082
+ ....: @cached_method
3083
+ ....: def __hash__(self):
3084
+ ....: print("compute hash")
3085
+ ....: return int(5)
3086
+ ....: @cached_method
3087
+ ....: def f(self, x):
3088
+ ....: print("computing cached method")
3089
+ ....: return x*2
3090
+ sage: c = C()
3091
+ sage: type(C.__hash__)
3092
+ <class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
3093
+ sage: hash(c)
3094
+ compute hash
3095
+ 5
3096
+
3097
+ When calling a cached method for the second time with the same arguments,
3098
+ the value is gotten from the cache, so that a new computation is not
3099
+ needed::
3100
+
3101
+ sage: hash(c)
3102
+ 5
3103
+ sage: c.f(4)
3104
+ computing cached method
3105
+ 8
3106
+ sage: c.f(4) is c.f(4)
3107
+ True
3108
+
3109
+ Different instances have distinct caches::
3110
+
3111
+ sage: d = C()
3112
+ sage: d.f(4) is c.f(4)
3113
+ computing cached method
3114
+ False
3115
+ sage: d.f.clear_cache()
3116
+ sage: c.f(4)
3117
+ 8
3118
+ sage: d.f(4)
3119
+ computing cached method
3120
+ 8
3121
+
3122
+ Using cached methods for the hash and other special methods was
3123
+ implemented in :issue:`12601`, by means of :class:`CachedSpecialMethod`. We
3124
+ show that it is used behind the scenes::
3125
+
3126
+ sage: cached_method(c.__hash__)
3127
+ <sage.misc.cachefunc.CachedSpecialMethod object at ...>
3128
+ sage: cached_method(c.f)
3129
+ <sage.misc.cachefunc.CachedMethod object at ...>
3130
+
3131
+ The parameter ``do_pickle`` can be used if the contents of the cache should be
3132
+ stored in a pickle of the cached method. This can be dangerous with special
3133
+ methods such as ``__hash__``::
3134
+
3135
+ sage: class C:
3136
+ ....: @cached_method(do_pickle=True)
3137
+ ....: def __hash__(self):
3138
+ ....: return id(self)
3139
+
3140
+ sage: import __main__
3141
+ sage: __main__.C = C
3142
+ sage: c = C()
3143
+ sage: hash(c) # random output
3144
+ sage: d = loads(dumps(c))
3145
+ sage: hash(d) == hash(c)
3146
+ True
3147
+
3148
+ However, the contents of a method's cache are not pickled unless ``do_pickle``
3149
+ is set::
3150
+
3151
+ sage: class C:
3152
+ ....: @cached_method
3153
+ ....: def __hash__(self):
3154
+ ....: return id(self)
3155
+
3156
+ sage: __main__.C = C
3157
+ sage: c = C()
3158
+ sage: hash(c) # random output
3159
+ sage: d = loads(dumps(c))
3160
+ sage: hash(d) == hash(c)
3161
+ False
3162
+ """
3163
+ cdef str fname = name or f.__name__
3164
+ if fname in special_method_names:
3165
+ return CachedSpecialMethod(f, name, key=key, do_pickle=do_pickle)
3166
+ return CachedMethod(f, name, key=key, do_pickle=do_pickle)
3167
+
3168
+
3169
+ cdef class CachedInParentMethod(CachedMethod):
3170
+ r"""
3171
+ A decorator that creates a cached version of an instance
3172
+ method of a class.
3173
+
3174
+ In contrast to :class:`CachedMethod`,
3175
+ the cache dictionary is an attribute of the parent of
3176
+ the instance to which the method belongs.
3177
+
3178
+ ASSUMPTION:
3179
+
3180
+ This way of caching works only if
3181
+
3182
+ - the instances *have* a parent, and
3183
+ - the instances are hashable (they are part of the cache key) or they
3184
+ define :meth:`sage.structure.sage_object.SageObject._cache_key`
3185
+
3186
+ NOTE:
3187
+
3188
+ For proper behavior, the method must be a pure function (no side effects).
3189
+ If this decorator is used on a method, it will have identical output on
3190
+ equal elements. This is since the element is part of the hash key.
3191
+ Arguments to the method must be hashable or define
3192
+ :meth:`sage.structure.sage_object.SageObject._cache_key`. The instance it
3193
+ is assigned to must be hashable.
3194
+
3195
+ Examples can be found at :mod:`~sage.misc.cachefunc`.
3196
+ """
3197
+
3198
+ def __init__(self, f, name: str | None = None, key=None, do_pickle: bool = False) -> None:
3199
+ """
3200
+ Construct a new method with cache stored in the parent of the instance.
3201
+
3202
+ See also ``cached_method`` and ``cached_function``.
3203
+
3204
+ EXAMPLES::
3205
+
3206
+ sage: class MyParent(Parent):
3207
+ ....: pass
3208
+ sage: class Foo: # indirect doctest
3209
+ ....: def __init__(self, x):
3210
+ ....: self._x = x
3211
+ ....: self._parent = MyParent()
3212
+ ....: def parent(self):
3213
+ ....: return self._parent
3214
+ ....: @cached_in_parent_method
3215
+ ....: def f(self):
3216
+ ....: return self._x^2
3217
+ sage: a = Foo(2)
3218
+ sage: a.f()
3219
+ 4
3220
+ sage: b = Foo(3)
3221
+ sage: b.f()
3222
+ 9
3223
+ sage: hasattr(a.parent(), '_cache__element_f')
3224
+ True
3225
+
3226
+ For speeding up internal computations, this dictionary
3227
+ is also accessible as an attribute of the CachedMethodCaller
3228
+ (by :issue:`8611`)::
3229
+
3230
+ sage: a.parent()._cache__element_f is a.f.cache
3231
+ True
3232
+
3233
+ TESTS:
3234
+
3235
+ Test that ``key`` works::
3236
+
3237
+ sage: class A():
3238
+ ....: def __init__(self):
3239
+ ....: self._parent = MyParent()
3240
+ ....: def parent(self): return self._parent
3241
+ ....: def _f_normalize(self, x, algorithm): return x
3242
+ ....: @cached_in_parent_method(key=_f_normalize)
3243
+ ....: def f(self, x, algorithm='default'): return x
3244
+ sage: a = A()
3245
+ sage: a.f(1, algorithm='default') is a.f(1) is a.f(1, algorithm='algorithm')
3246
+ True
3247
+
3248
+ Test that ``do_pickle`` works. Usually the contents of the cache are not
3249
+ pickled::
3250
+
3251
+ sage: class A():
3252
+ ....: def __init__(self):
3253
+ ....: self._parent = MyParent()
3254
+ ....: def parent(self): return self._parent
3255
+ ....: @cached_in_parent_method
3256
+ ....: def f(self, x): return x
3257
+ sage: import __main__
3258
+ sage: __main__.A = A
3259
+ sage: __main__.MyParent = MyParent
3260
+ sage: a = A()
3261
+ sage: a.f(1)
3262
+ 1
3263
+ sage: len(a.f.cache)
3264
+ 1
3265
+ sage: b = loads(dumps(a))
3266
+ sage: len(b.f.cache)
3267
+ 0
3268
+
3269
+ Pickling can be enabled with ``do_pickle``::
3270
+
3271
+ sage: class A():
3272
+ ....: def __init__(self):
3273
+ ....: self._parent = MyParent()
3274
+ ....: def parent(self): return self._parent
3275
+ ....: @cached_in_parent_method(do_pickle=True)
3276
+ ....: def f(self, x): return x
3277
+ sage: __main__.A = A
3278
+ sage: a = A()
3279
+ sage: a.f(1)
3280
+ 1
3281
+ sage: len(a.f.cache)
3282
+ 1
3283
+ sage: b = loads(dumps(a))
3284
+ sage: len(b.f.cache)
3285
+ 1
3286
+ """
3287
+ self._cache_name = '_cache__' + 'element_' + (name or f.__name__)
3288
+ self._cachedfunc = CachedFunction(f, classmethod=True, name=name, key=key, do_pickle=do_pickle)
3289
+
3290
+ cpdef _get_instance_cache(self, inst):
3291
+ """
3292
+ Return the cache dictionary, which is stored in the parent.
3293
+
3294
+ EXAMPLES::
3295
+
3296
+ sage: class MyParent(Parent):
3297
+ ....: pass
3298
+ sage: class Foo:
3299
+ ....: def __init__(self, x):
3300
+ ....: self._x = x
3301
+ ....: _parent = MyParent()
3302
+ ....: def parent(self):
3303
+ ....: return self._parent
3304
+ ....: def __eq__(self, other):
3305
+ ....: return self._x^2 == other._x^2
3306
+ ....: def __hash__(self):
3307
+ ....: return hash(self._x^2)
3308
+ ....: def __repr__(self):
3309
+ ....: return 'My %s'%self._x
3310
+ ....: @cached_in_parent_method
3311
+ ....: def f(self):
3312
+ ....: return self._x^3
3313
+ sage: a = Foo(2)
3314
+ sage: a.f()
3315
+ 8
3316
+ sage: a.f.cache #indirect doctest
3317
+ {(My 2, ((), ())): 8}
3318
+
3319
+ Since the key for the cache depends on equality of
3320
+ the instances, we obtain *identical* result for
3321
+ *equal* instance - even though in this particular
3322
+ example the result is wrong::
3323
+
3324
+ sage: b = Foo(-2)
3325
+ sage: a is not b
3326
+ True
3327
+ sage: a == b
3328
+ True
3329
+ sage: b.f() is a.f()
3330
+ True
3331
+
3332
+ Non-equal instances do not share the result of
3333
+ the cached method, but they do share the cache::
3334
+
3335
+ sage: c = Foo(3)
3336
+ sage: c.f()
3337
+ 27
3338
+ sage: c.f.cache is a.f.cache #indirect doctest
3339
+ True
3340
+
3341
+ Note that the cache is also available as an
3342
+ attribute of the cached method, which speeds
3343
+ up internal computations::
3344
+
3345
+ sage: a.f.cache is b.f.cache is c.f._cachedmethod._get_instance_cache(c)
3346
+ True
3347
+ """
3348
+ default = {} if self._cachedfunc.do_pickle else NonpicklingDict()
3349
+ if inst is None:
3350
+ return default
3351
+ try:
3352
+ P = inst.parent()
3353
+ return P.__dict__.setdefault(self._cache_name, default)
3354
+ except AttributeError:
3355
+ pass
3356
+ if not hasattr(P, '_cached_methods'):
3357
+ raise TypeError("The parent of this element does not allow attribute assignment\n" +
3358
+ " and does not descend from the Parent base class.\n" +
3359
+ " Cannot use CachedInParentMethod.")
3360
+ if P._cached_methods is None:
3361
+ P._cached_methods = {}
3362
+ return (<dict>P._cached_methods).setdefault(self._cache_name, default)
3363
+
3364
+ def __get__(self, inst, cls) -> GloballyCachedMethodCaller:
3365
+ """
3366
+ Get a CachedMethodCaller bound to this specific instance of
3367
+ the class of the cached-in-parent method.
3368
+ """
3369
+ Caller = GloballyCachedMethodCaller(self, inst, cache=self._get_instance_cache(inst), key=self._cachedfunc.key, do_pickle=self._cachedfunc.do_pickle)
3370
+ try:
3371
+ setattr(inst, self._cachedfunc.__name__, Caller)
3372
+ except AttributeError:
3373
+ pass
3374
+ return Caller
3375
+
3376
+
3377
+ cached_in_parent_method = decorator_keywords(CachedInParentMethod)
3378
+
3379
+
3380
+ class FileCache():
3381
+ """
3382
+ :class:`FileCache` is a dictionary-like class which stores keys
3383
+ and values on disk. The keys take the form of a tuple ``(A,K)``
3384
+
3385
+ - ``A`` -- tuple of objects ``t`` where each ``t`` is an
3386
+ exact object which is uniquely identified by a short string
3387
+
3388
+ - ``K`` -- tuple of tuples ``(s,v)`` where ``s`` is a valid
3389
+ variable name and ``v`` is an exact object which is uniquely
3390
+ identified by a short string with letters [a-zA-Z0-9-._]
3391
+
3392
+ The primary use case is the :class:`DiskCachedFunction`. If
3393
+ ``memory_cache == True``, we maintain a cache of objects seen
3394
+ during this session in memory -- but we don't load them from
3395
+ disk until necessary. The keys and values are stored in a
3396
+ pair of files:
3397
+
3398
+ - ``prefix-argstring.key.sobj`` contains the ``key`` only,
3399
+ - ``prefix-argstring.sobj`` contains the tuple ``(key,val)``
3400
+
3401
+ where ``self[key] == val``.
3402
+
3403
+ .. NOTE::
3404
+
3405
+ We assume that each :class:`FileCache` lives in its own directory.
3406
+ Use **extreme** caution if you wish to break that assumption.
3407
+ """
3408
+ def __init__(self, dir, prefix='', memory_cache=False) -> None:
3409
+ """
3410
+ EXAMPLES::
3411
+
3412
+ sage: from sage.misc.cachefunc import FileCache
3413
+ sage: dir = tmp_dir()
3414
+ sage: FC = FileCache(dir, memory_cache = True)
3415
+ sage: FC[((),())] = 1
3416
+ sage: FC[((1,2),())] = 2
3417
+ sage: FC[((),())]
3418
+ 1
3419
+ """
3420
+ if not dir or dir[-1] != '/':
3421
+ dir += '/'
3422
+ self._dir = dir
3423
+ os.makedirs(dir, exist_ok=True)
3424
+
3425
+ self._prefix = prefix + '-'
3426
+
3427
+ if memory_cache:
3428
+ self._cache = {}
3429
+ else:
3430
+ self._cache = None
3431
+
3432
+ def file_list(self) -> list[str]:
3433
+ """
3434
+ Return the list of files corresponding to ``self``.
3435
+
3436
+ EXAMPLES::
3437
+
3438
+ sage: from sage.misc.cachefunc import FileCache
3439
+ sage: dir = tmp_dir()
3440
+ sage: FC = FileCache(dir, memory_cache = True, prefix='t')
3441
+ sage: FC[((),())] = 1
3442
+ sage: FC[((1,2),())] = 2
3443
+ sage: FC[((1,),(('a',1),))] = 3
3444
+ sage: for f in sorted(FC.file_list()): print(f[len(dir):])
3445
+ t-.key.sobj
3446
+ t-.sobj
3447
+ t-1_2.key.sobj
3448
+ t-1_2.sobj
3449
+ t-a-1.1.key.sobj
3450
+ t-a-1.1.sobj
3451
+ """
3452
+ files = []
3453
+ prefix = self._prefix
3454
+ dir = self._dir
3455
+ l = len(prefix)
3456
+ for f in os.listdir(dir):
3457
+ if f[:l] == prefix:
3458
+ files.append(dir + f)
3459
+ return files
3460
+
3461
+ def items(self) -> list[tuple[Any, Any]]:
3462
+ """
3463
+ Return a list of tuples ``(k,v)`` where ``self[k] = v``.
3464
+
3465
+ EXAMPLES::
3466
+
3467
+ sage: from sage.misc.cachefunc import FileCache
3468
+ sage: dir = tmp_dir()
3469
+ sage: FC = FileCache(dir, memory_cache = False)
3470
+ sage: FC[((),())] = 1
3471
+ sage: FC[((1,2),())] = 2
3472
+ sage: FC[((1,),(('a',1),))] = 3
3473
+ sage: I = FC.items()
3474
+ sage: I.sort(); I
3475
+ [(((), ()), 1), (((1,), (('a', 1),)), 3), (((1, 2), ()), 2)]
3476
+ """
3477
+ return [(k, self[k]) for k in self]
3478
+
3479
+ def values(self) -> list:
3480
+ """
3481
+ Return a list of values that are stored in ``self``.
3482
+
3483
+ EXAMPLES::
3484
+
3485
+ sage: from sage.misc.cachefunc import FileCache
3486
+ sage: dir = tmp_dir()
3487
+ sage: FC = FileCache(dir, memory_cache = False)
3488
+ sage: FC[((),())] = 1
3489
+ sage: FC[((1,2),())] = 2
3490
+ sage: FC[((1,),(('a',1),))] = 3
3491
+ sage: FC[((),(('a',1),))] = 4
3492
+ sage: v = FC.values()
3493
+ sage: v.sort(); v
3494
+ [1, 2, 3, 4]
3495
+ """
3496
+ return [self[k] for k in self]
3497
+
3498
+ def __iter__(self) -> Iterator:
3499
+ """
3500
+ Return a list of keys of ``self``.
3501
+
3502
+ EXAMPLES::
3503
+
3504
+ sage: from sage.misc.cachefunc import FileCache
3505
+ sage: dir = tmp_dir()
3506
+ sage: FC = FileCache(dir, memory_cache = False)
3507
+ sage: FC[((),())] = 1
3508
+ sage: FC[((1,2),())] = 2
3509
+ sage: FC[((1,),(('a',1),))] = 3
3510
+ sage: for k in sorted(FC): print(k)
3511
+ ((), ())
3512
+ ((1,), (('a', 1),))
3513
+ ((1, 2), ())
3514
+ """
3515
+ return iter(self.keys())
3516
+
3517
+ def keys(self) -> list:
3518
+ """
3519
+ Return a list of keys ``k`` where ``self[k]`` is defined.
3520
+
3521
+ EXAMPLES::
3522
+
3523
+ sage: from sage.misc.cachefunc import FileCache
3524
+ sage: dir = tmp_dir()
3525
+ sage: FC = FileCache(dir, memory_cache = False)
3526
+ sage: FC[((),())] = 1
3527
+ sage: FC[((1,2),())] = 2
3528
+ sage: FC[((1,),(('a',1),))] = 3
3529
+ sage: K = FC.keys()
3530
+ sage: K.sort(); K
3531
+ [((), ()), ((1,), (('a', 1),)), ((1, 2), ())]
3532
+ """
3533
+ cdef list K = []
3534
+ from sage.misc.persist import load
3535
+ for f in self.file_list():
3536
+ if f[-9:] == '.key.sobj':
3537
+ K.append(load(f))
3538
+ return K
3539
+
3540
+ def clear(self) -> None:
3541
+ """
3542
+ Clear all key, value pairs from ``self`` and unlink the associated files
3543
+ from the file cache.
3544
+
3545
+ EXAMPLES::
3546
+
3547
+ sage: from sage.misc.cachefunc import FileCache
3548
+ sage: dir = tmp_dir()
3549
+ sage: FC1 = FileCache(dir, memory_cache=False, prefix='foo')
3550
+ sage: FC2 = FileCache(dir, memory_cache=False, prefix='foo')
3551
+ sage: k1 = ((), (('a', 1),))
3552
+ sage: t1 = randint(0, 1000)
3553
+ sage: k2 = ((), (('b', 1),))
3554
+ sage: t2 = randint(0, 1000)
3555
+ sage: FC1[k1] = t1
3556
+ sage: FC2[k2] = t2
3557
+ sage: FC2.clear()
3558
+ sage: k1 in FC1
3559
+ False
3560
+ sage: k2 in FC1
3561
+ False
3562
+ """
3563
+ for k in self:
3564
+ del self[k]
3565
+
3566
+ def _filename(self, key) -> str:
3567
+ """
3568
+ Compute the filename associated with a certain key.
3569
+
3570
+ EXAMPLES::
3571
+
3572
+ sage: from sage.misc.cachefunc import FileCache
3573
+ sage: dir = tmp_dir()
3574
+ sage: FC = FileCache(dir, memory_cache = False, prefix='foo')
3575
+ sage: N = FC._filename(((1,2), (('a',1),('b',2))))
3576
+ sage: print(N[len(dir):])
3577
+ foo-a-1_b-2.1_2
3578
+ sage: N = FC._filename(((), (('a',1),('b',2))))
3579
+ sage: print(N[len(dir):])
3580
+ foo-a-1_b-2
3581
+ sage: N = FC._filename(((1,2), ()))
3582
+ sage: print(N[len(dir):])
3583
+ foo-1_2
3584
+ """
3585
+ a, k = key
3586
+ kwdstr = '_'.join('%s-%s' % x for x in k)
3587
+ argstr = '_'.join('%s' % x for x in a)
3588
+ if kwdstr and argstr:
3589
+ keystr = kwdstr + '.' + argstr
3590
+ else:
3591
+ keystr = kwdstr + argstr
3592
+ return self._dir + self._prefix + keystr
3593
+
3594
+ def __contains__(self, key) -> bool:
3595
+ """
3596
+ Return ``True`` if ``self[key]`` is defined and ``False`` otherwise.
3597
+
3598
+ EXAMPLES::
3599
+
3600
+ sage: from sage.misc.cachefunc import FileCache
3601
+ sage: dir = tmp_dir()
3602
+ sage: FC = FileCache(dir, memory_cache = False, prefix='foo')
3603
+ sage: k = ((),(('a',1),))
3604
+ sage: FC[k] = True
3605
+ sage: k in FC
3606
+ True
3607
+ sage: ((),()) in FC
3608
+ False
3609
+ """
3610
+ return os.path.exists(self._filename(key) + '.key.sobj')
3611
+
3612
+ def __getitem__(self, key):
3613
+ """
3614
+ Return the value set by ``self[key] = val``, in this session
3615
+ or a previous one.
3616
+
3617
+ EXAMPLES::
3618
+
3619
+ sage: from sage.misc.cachefunc import FileCache
3620
+ sage: dir = tmp_dir()
3621
+ sage: FC1 = FileCache(dir, memory_cache = False, prefix='foo')
3622
+ sage: FC2 = FileCache(dir, memory_cache = False, prefix='foo')
3623
+ sage: k = ((),(('a',1),))
3624
+ sage: t = randint(0, 1000)
3625
+ sage: FC1[k] = t
3626
+ sage: FC2[k] == FC1[k] == t
3627
+ True
3628
+ sage: FC1[(1,2),(('a',4),('b',2))]
3629
+ Traceback (most recent call last):
3630
+ ...
3631
+ KeyError: ((1, 2), (('a', 4), ('b', 2)))
3632
+ """
3633
+ from sage.misc.persist import load
3634
+
3635
+ cache = self._cache
3636
+ if cache is not None:
3637
+ if key in cache:
3638
+ return cache[key]
3639
+
3640
+ f = self._filename(key) + '.sobj'
3641
+ try:
3642
+ k, v = load(f)
3643
+ except IOError:
3644
+ raise KeyError(key)
3645
+ if k != key:
3646
+ raise RuntimeError("cache corrupted")
3647
+
3648
+ if cache is not None:
3649
+ cache[key] = v
3650
+ return v
3651
+
3652
+ def __setitem__(self, key, value) -> None:
3653
+ """
3654
+ Set ``self[key] = value`` and stores both key and value on
3655
+ disk.
3656
+
3657
+ EXAMPLES::
3658
+
3659
+ sage: from sage.misc.cachefunc import FileCache
3660
+ sage: dir = tmp_dir()
3661
+ sage: FC1 = FileCache(dir, memory_cache = False, prefix='foo')
3662
+ sage: FC2 = FileCache(dir, memory_cache = False, prefix='foo')
3663
+ sage: k = ((),(('a',1),))
3664
+ sage: t = randint(0, 1000)
3665
+ sage: FC1[k] = t
3666
+ sage: FC2[k] == t
3667
+ True
3668
+ sage: FC1[k] = 2000
3669
+ sage: FC2[k]!= t
3670
+ True
3671
+ """
3672
+ from sage.misc.persist import save
3673
+
3674
+ f = self._filename(key)
3675
+
3676
+ save(key, f+'.key.sobj')
3677
+ save((key, value), f + '.sobj')
3678
+ if self._cache is not None:
3679
+ self._cache[key] = value
3680
+
3681
+ def __delitem__(self, key) -> None:
3682
+ """
3683
+ Delete the ``key, value`` pair from ``self`` and unlink the associated
3684
+ files from the file cache.
3685
+
3686
+ EXAMPLES::
3687
+
3688
+ sage: from sage.misc.cachefunc import FileCache
3689
+ sage: dir = tmp_dir()
3690
+ sage: FC1 = FileCache(dir, memory_cache = False, prefix='foo')
3691
+ sage: FC2 = FileCache(dir, memory_cache = False, prefix='foo')
3692
+ sage: k = ((),(('a',1),))
3693
+ sage: t = randint(0, 1000)
3694
+ sage: FC1[k] = t
3695
+ sage: del FC2[k]
3696
+ sage: k in FC1
3697
+ False
3698
+ """
3699
+ f = self._filename(key)
3700
+ cache = self._cache
3701
+ if cache is not None and key in cache:
3702
+ del self._cache[key]
3703
+ if os.path.exists(f + '.sobj'):
3704
+ os.remove(f + '.sobj')
3705
+ if os.path.exists(f + '.key.sobj'):
3706
+ os.remove(f + '.key.sobj')
3707
+
3708
+
3709
+ class DiskCachedFunction(CachedFunction):
3710
+ """
3711
+ Works similar to CachedFunction, but instead, we keep the
3712
+ cache on disk (optionally, we keep it in memory too).
3713
+
3714
+ EXAMPLES::
3715
+
3716
+ sage: from sage.misc.cachefunc import DiskCachedFunction
3717
+ sage: dir = tmp_dir()
3718
+ sage: factor = DiskCachedFunction(factor, dir, memory_cache=True)
3719
+ sage: f = factor(2775); f
3720
+ 3 * 5^2 * 37
3721
+ sage: f is factor(2775)
3722
+ True
3723
+ """
3724
+ def __init__(self, f, dir, memory_cache=False, key=None) -> None:
3725
+ """
3726
+ EXAMPLES::
3727
+
3728
+ sage: from sage.misc.cachefunc import DiskCachedFunction
3729
+ sage: def foo(x): sleep(x)
3730
+ sage: dir = tmp_dir()
3731
+ sage: bar = DiskCachedFunction(foo, dir, memory_cache = False)
3732
+ sage: w = walltime()
3733
+ sage: for i in range(10): bar(1)
3734
+ sage: walltime(w) < 2
3735
+ True
3736
+ """
3737
+ CachedFunction.__init__(self, f, key=key)
3738
+ prefix = f.__name__
3739
+ self.cache = FileCache(dir, prefix=prefix, memory_cache = memory_cache)
3740
+
3741
+
3742
+ class disk_cached_function:
3743
+ """
3744
+ Decorator for :class:`DiskCachedFunction`.
3745
+
3746
+ EXAMPLES::
3747
+
3748
+ sage: dir = tmp_dir()
3749
+ sage: @disk_cached_function(dir)
3750
+ ....: def foo(x): return next_prime(2^x)%x
3751
+ sage: x = foo(200); x # needs sage.libs.pari
3752
+ 11
3753
+ sage: @disk_cached_function(dir)
3754
+ ....: def foo(x): return 1/x
3755
+ sage: foo(200) # needs sage.libs.pari
3756
+ 11
3757
+ sage: foo.clear_cache()
3758
+ sage: foo(200)
3759
+ 1/200
3760
+ """
3761
+ def __init__(self, dir, memory_cache=False, key=None) -> None:
3762
+ """
3763
+ EXAMPLES::
3764
+
3765
+ sage: dir = tmp_dir()
3766
+ sage: @disk_cached_function(dir, memory_cache=True)
3767
+ ....: def foo(x): return next_prime(2^x)
3768
+ sage: x = foo(200) # needs sage.libs.pari
3769
+ sage: x is foo(200) # needs sage.libs.pari
3770
+ True
3771
+ sage: @disk_cached_function(dir, memory_cache=False)
3772
+ ....: def foo(x): return next_prime(2^x)
3773
+ sage: x is foo(200) # needs sage.libs.pari
3774
+ False
3775
+ """
3776
+ self._dir = dir
3777
+ self._memory_cache = memory_cache
3778
+ self._key = key
3779
+
3780
+ def __call__(self, f) -> DiskCachedFunction:
3781
+ """
3782
+ EXAMPLES::
3783
+
3784
+ sage: dir = tmp_dir()
3785
+ sage: @disk_cached_function(dir)
3786
+ ....: def foo(x): return ModularSymbols(x)
3787
+ sage: foo(389) # needs sage.modular
3788
+ Modular Symbols space of dimension 65 for Gamma_0(389) of weight 2
3789
+ with sign 0 over Rational Field
3790
+ """
3791
+ return DiskCachedFunction(f, self._dir, memory_cache=self._memory_cache, key=self._key)
3792
+
3793
+
3794
+ # Add support for _instancedoc_
3795
+ from sage.misc.instancedoc import instancedoc
3796
+ instancedoc(CachedFunction)
3797
+ instancedoc(WeakCachedFunction)
3798
+ instancedoc(CachedMethodCaller)
3799
+ instancedoc(CachedMethodCallerNoArgs)
3800
+ instancedoc(GloballyCachedMethodCaller)
3801
+ instancedoc(DiskCachedFunction)