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