Skip to content

Commit cc231ef

Browse files
author
Release Manager
committed
gh-39061: Fix some errors in documentation of cachefunc See changes for detail. Basically some of the information was previously incorrect/misleading, some other information was unclear (e.g. "method bound to a class" is officially called ["unbound method"](https://docs.python.org/3/reference/datamodel.html)) ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. (none) - [x] I have created tests covering the changes. (no functional change) - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39061 Reported by: user202729 Reviewer(s): Julian Rüth, nbruin, user202729
2 parents f0581cb + bde5658 commit cc231ef

File tree

1 file changed

+81
-30
lines changed

1 file changed

+81
-30
lines changed

src/sage/misc/cachefunc.pyx

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,6 @@ approach is still needed for cpdef methods::
9797
sage: O.direct_method(5) is O.direct_method(5)
9898
True
9999
100-
In some cases, one would only want to keep the result in cache as long
101-
as there is any other reference to the result. By :issue:`12215`, this is
102-
enabled for :class:`~sage.structure.unique_representation.UniqueRepresentation`,
103-
which is used to create unique parents: If an algebraic structure, such
104-
as a finite field, is only temporarily used, then it will not stay in
105-
cache forever. That behaviour is implemented using ``weak_cached_function``,
106-
that behaves the same as ``cached_function``, except that it uses a
107-
:class:`~sage.misc.weak_dict.CachedWeakValueDictionary` for storing the results.
108-
109100
By :issue:`11115`, even if a parent does not allow attribute
110101
assignment, it can inherit a cached method from the parent class of a
111102
category (previously, the cache would have been broken)::
@@ -147,7 +138,7 @@ that has generally a slower attribute access, but fully supports
147138
cached methods. We remark, however, that cached methods are
148139
*much* faster if attribute access works. So, we expect that
149140
:class:`~sage.structure.element.ElementWithCachedMethod` will
150-
hardly by used.
141+
hardly be used.
151142
::
152143
153144
sage: # needs sage.misc.cython
@@ -415,6 +406,30 @@ the parent as its first argument::
415406
sage: d = a.add_bigoh(1) # needs sage.rings.padics
416407
sage: b._cache_key() == d._cache_key() # this would be True if the parents were not included
417408
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
418433
"""
419434

420435
# ****************************************************************************
@@ -1884,7 +1899,7 @@ cdef class CachedMethodCaller(CachedFunction):
18841899
sage: a.f(5) is a.f(y=1,x=5)
18851900
True
18861901
1887-
The method can be called as a bound function using the same cache::
1902+
The method can be called as an unbound function using the same cache::
18881903
18891904
sage: a.f(5) is Foo.f(a, 5)
18901905
True
@@ -1942,7 +1957,7 @@ cdef class CachedMethodCaller(CachedFunction):
19421957
True
19431958
"""
19441959
if self._instance is None:
1945-
# cached method bound to a class
1960+
# unbound cached method such as ``Foo.f``
19461961
instance = args[0]
19471962
args = args[1:]
19481963
return self._cachedmethod.__get__(instance)(*args, **kwds)
@@ -1989,7 +2004,7 @@ cdef class CachedMethodCaller(CachedFunction):
19892004
5
19902005
"""
19912006
if self._instance is None:
1992-
# cached method bound to a class
2007+
# unbound cached method such as ``CachedMethodTest.f``
19932008
instance = args[0]
19942009
args = args[1:]
19952010
return self._cachedmethod.__get__(instance).cached(*args, **kwds)
@@ -2550,16 +2565,17 @@ cdef class CachedMethod():
25502565
sage: a.f(3) is res
25512566
True
25522567
2553-
Note, however, that the :class:`CachedMethod` is replaced by a
2554-
:class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`
2555-
as soon as it is bound to an instance or class::
2568+
Note, however, that accessing the attribute directly will call :meth:`__get__`,
2569+
and returns a :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`.
2570+
2571+
::
25562572
25572573
sage: P.<a,b,c,d> = QQ[]
25582574
sage: I = P*[a,b]
25592575
sage: type(I.__class__.gens)
25602576
<class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
2561-
2562-
So, you would hardly ever see an instance of this class alive.
2577+
sage: type(I.__class__.__dict__["gens"])
2578+
<class 'sage.misc.cachefunc.CachedMethod'>
25632579
25642580
The parameter ``key`` can be used to pass a function which creates a
25652581
custom cache key for inputs. In the following example, this parameter is
@@ -2642,13 +2658,17 @@ cdef class CachedMethod():
26422658
sage: a.f0()
26432659
4
26442660
2645-
The computations in method ``f`` are tried to store in a
2646-
dictionary assigned to the instance ``a``::
2661+
For methods with parameters, the results of method ``f`` is attempted
2662+
to be stored in a dictionary attribute of the instance ``a``::
26472663
26482664
sage: hasattr(a, '_cache__f')
26492665
True
26502666
sage: a._cache__f
26512667
{((2,), ()): 4}
2668+
sage: a._cache_f0
2669+
Traceback (most recent call last):
2670+
...
2671+
AttributeError: 'Foo' object has no attribute '_cache_f0'...
26522672
26532673
As a shortcut, useful to speed up internal computations,
26542674
the same dictionary is also available as an attribute
@@ -2696,6 +2716,8 @@ cdef class CachedMethod():
26962716
def __call__(self, inst, *args, **kwds):
26972717
"""
26982718
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.
26992721
27002722
INPUT:
27012723
@@ -2747,20 +2769,48 @@ cdef class CachedMethod():
27472769
....: def f(self, n=2):
27482770
....: return self._x^n
27492771
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+
{}
27502785
sage: a.f()
27512786
4
2787+
sage: a.f.cache
2788+
{((2,), ()): 4}
2789+
sage: a._cache__f
2790+
{((2,), ()): 4}
27522791
2753-
Note that we cannot provide a direct test, since ``a.f`` is
2754-
an instance of :class:`CachedMethodCaller`. But during its
2755-
initialisation, this method was called in order to provide the
2756-
cached method caller with its cache, and, if possible, assign
2757-
it to an attribute of ``a``. So, the following is an indirect
2758-
doctest::
2792+
Testing the method directly::
27592793
2760-
sage: a.f.cache # indirect doctest
2761-
{((2,), ()): 4}
2794+
sage: a = Foo(2)
2795+
sage: hasattr(a, "_cache__f")
2796+
False
2797+
sage: Foo.__dict__["f"]._get_instance_cache(a)
2798+
{}
27622799
sage: a._cache__f
2800+
{}
2801+
sage: a.f()
2802+
4
2803+
sage: Foo.__dict__["f"]._get_instance_cache(a)
27632804
{((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'>
27642814
"""
27652815
default = {} if self._cachedfunc.do_pickle else NonpicklingDict()
27662816
try:
@@ -2870,8 +2920,9 @@ cdef class CachedSpecialMethod(CachedMethod):
28702920
28712921
For new style classes ``C``, it is not possible to override a special
28722922
method, such as ``__hash__``, in the ``__dict__`` of an instance ``c`` of
2873-
``C``, because Python will for efficiency reasons always use what is
2874-
provided by the class, not by the instance.
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>`_.
28752926
28762927
By consequence, if ``__hash__`` would be wrapped by using
28772928
:class:`CachedMethod`, then ``hash(c)`` will access ``C.__hash__`` and bind

0 commit comments

Comments
 (0)