Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some errors in documentation of cachefunc #39061

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
111 changes: 81 additions & 30 deletions src/sage/misc/cachefunc.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,6 @@ approach is still needed for cpdef methods::
sage: O.direct_method(5) is O.direct_method(5)
True

In some cases, one would only want to keep the result in cache as long
as there is any other reference to the result. By :issue:`12215`, this is
enabled for :class:`~sage.structure.unique_representation.UniqueRepresentation`,
which is used to create unique parents: If an algebraic structure, such
as a finite field, is only temporarily used, then it will not stay in
cache forever. That behaviour is implemented using ``weak_cached_function``,
that behaves the same as ``cached_function``, except that it uses a
:class:`~sage.misc.weak_dict.CachedWeakValueDictionary` for storing the results.

By :issue:`11115`, even if a parent does not allow attribute
assignment, it can inherit a cached method from the parent class of a
category (previously, the cache would have been broken)::
Expand Down Expand Up @@ -147,7 +138,7 @@ that has generally a slower attribute access, but fully supports
cached methods. We remark, however, that cached methods are
*much* faster if attribute access works. So, we expect that
:class:`~sage.structure.element.ElementWithCachedMethod` will
hardly by used.
hardly be used.
::

sage: # needs sage.misc.cython
Expand Down Expand Up @@ -415,6 +406,30 @@ the parent as its first argument::
sage: d = a.add_bigoh(1) # needs sage.rings.padics
sage: b._cache_key() == d._cache_key() # this would be True if the parents were not included
False

Note that shallow copy of mutable objects may behave unexpectedly::

sage: class Foo:
....: @cached_method
....: def f(self):
....: return self.x
sage: from copy import copy, deepcopy
sage: a = Foo()
sage: a.x = 1
sage: a.f()
1
sage: b = copy(a)
sage: b.x = 2
sage: b.f() # incorrect!
1
sage: b.f is a.f # this is the problem
True
sage: b = deepcopy(a)
sage: b.x = 2
sage: b.f() # correct
2
sage: b.f is a.f
False
"""

# ****************************************************************************
Expand Down Expand Up @@ -1884,7 +1899,7 @@ cdef class CachedMethodCaller(CachedFunction):
sage: a.f(5) is a.f(y=1,x=5)
True

The method can be called as a bound function using the same cache::
The method can be called as an unbound function using the same cache::

sage: a.f(5) is Foo.f(a, 5)
True
Expand Down Expand Up @@ -1942,7 +1957,7 @@ cdef class CachedMethodCaller(CachedFunction):
True
"""
if self._instance is None:
# cached method bound to a class
# unbound cached method such as ``Foo.f``
instance = args[0]
args = args[1:]
return self._cachedmethod.__get__(instance)(*args, **kwds)
Expand Down Expand Up @@ -1989,7 +2004,7 @@ cdef class CachedMethodCaller(CachedFunction):
5
"""
if self._instance is None:
# cached method bound to a class
# unbound cached method such as ``CachedMethodTest.f``
instance = args[0]
args = args[1:]
return self._cachedmethod.__get__(instance).cached(*args, **kwds)
Expand Down Expand Up @@ -2550,16 +2565,17 @@ cdef class CachedMethod():
sage: a.f(3) is res
True

Note, however, that the :class:`CachedMethod` is replaced by a
:class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`
as soon as it is bound to an instance or class::
Note, however, that accessing the attribute directly will call :meth:`__get__`,
and returns a :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`.

::

sage: P.<a,b,c,d> = QQ[]
sage: I = P*[a,b]
sage: type(I.__class__.gens)
<class 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>

So, you would hardly ever see an instance of this class alive.
sage: type(I.__class__.__dict__["gens"])
<class 'sage.misc.cachefunc.CachedMethod'>

The parameter ``key`` can be used to pass a function which creates a
custom cache key for inputs. In the following example, this parameter is
Expand Down Expand Up @@ -2642,13 +2658,17 @@ cdef class CachedMethod():
sage: a.f0()
4

The computations in method ``f`` are tried to store in a
dictionary assigned to the instance ``a``::
For methods with parameters, the results of method ``f`` is attempted
to be stored in a dictionary attribute of the instance ``a``::

sage: hasattr(a, '_cache__f')
True
sage: a._cache__f
{((2,), ()): 4}
sage: a._cache_f0
Traceback (most recent call last):
...
AttributeError: 'Foo' object has no attribute '_cache_f0'...

As a shortcut, useful to speed up internal computations,
the same dictionary is also available as an attribute
Expand Down Expand Up @@ -2696,6 +2716,8 @@ cdef class CachedMethod():
def __call__(self, inst, *args, **kwds):
"""
Call the cached method as a function on an instance.
This code path is not used directly except in a few rare cases,
see examples for details.

INPUT:

Expand Down Expand Up @@ -2747,20 +2769,48 @@ cdef class CachedMethod():
....: def f(self, n=2):
....: return self._x^n
sage: a = Foo(2)

Initially ``_cache__f`` is not an attribute of ``a``::

sage: hasattr(a, "_cache__f")
False

When the attribute is accessed (thus ``__get__`` is called),
the cache is created and assigned to the attribute::

sage: a.f
Cached version of <function Foo.f at 0x...>
sage: a._cache__f
{}
sage: a.f()
4
sage: a.f.cache
{((2,), ()): 4}
sage: a._cache__f
{((2,), ()): 4}

Note that we cannot provide a direct test, since ``a.f`` is
an instance of :class:`CachedMethodCaller`. But during its
initialisation, this method was called in order to provide the
cached method caller with its cache, and, if possible, assign
it to an attribute of ``a``. So, the following is an indirect
doctest::
Testing the method directly::

sage: a.f.cache # indirect doctest
{((2,), ()): 4}
sage: a = Foo(2)
sage: hasattr(a, "_cache__f")
False
sage: Foo.__dict__["f"]._get_instance_cache(a)
{}
sage: a._cache__f
{}
sage: a.f()
4
sage: Foo.__dict__["f"]._get_instance_cache(a)
{((2,), ()): 4}

Using ``__dict__`` is needed to access this function because
``Foo.f`` would call ``__get__`` and thus create a
:class:`CachedMethodCaller`::

sage: type(Foo.f)
<class 'sage.misc.cachefunc.CachedMethodCaller'>
sage: type(Foo.__dict__["f"])
<class 'sage.misc.cachefunc.CachedMethod'>
"""
default = {} if self._cachedfunc.do_pickle else NonpicklingDict()
try:
Expand Down Expand Up @@ -2870,8 +2920,9 @@ cdef class CachedSpecialMethod(CachedMethod):

For new style classes ``C``, it is not possible to override a special
method, such as ``__hash__``, in the ``__dict__`` of an instance ``c`` of
``C``, because Python will for efficiency reasons always use what is
provided by the class, not by the instance.
``C``, because Python will always use what is provided by the class, not
by the instance to avoid metaclass confusion. See
`<https://docs.python.org/3/reference/datamodel.html#special-method-lookup>`_.

By consequence, if ``__hash__`` would be wrapped by using
:class:`CachedMethod`, then ``hash(c)`` will access ``C.__hash__`` and bind
Expand Down
Loading