# HG changeset patch
# User Simon King <simon.king@uni-jena.de>
# Date 1305527781 -7200
# Node ID 63979466255e0f03a97d4f4209b022e8d2bf47ac
# Parent  d60cb5b380ced1223ffbd3a3706b38814a7daee5
#11115: lazy atributes and cached methods for elements without attribute assignment

diff --git a/sage/misc/cachefunc.pyx b/sage/misc/cachefunc.pyx
--- a/sage/misc/cachefunc.pyx
+++ b/sage/misc/cachefunc.pyx
@@ -97,29 +97,60 @@
     sage: cython('\n'.join(cython_code))
     sage: C = MyCategory()
 
-To make cacheing work on the elements, we need to provide it with
-a public attribute ``__cached_methods``, as in the second class below::
+In order to keep the memory footprint of elements small, it was
+decided to not support the same freedom of using cached methods
+for elements: If an instance of a class derived from
+:class:`~sage.structure.element.Element` does not allow attribute
+assignment, then a cached method inherited from the category of
+its parent will break, as in the class ``MyBrokenElement`` below.
 
-    sage: cython_code = ["from sage.structure.element cimport Element",
+However, there is a class :class:`~sage.structure.element.ElementWithCachedMethod`
+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.
+::
+
+    sage: cython_code = ["from sage.structure.element cimport Element, ElementWithCachedMethod",
     ... "cdef class MyBrokenElement(Element):",
     ... "    cdef public object x",
     ... "    def __init__(self,P,x):",
     ... "        self.x=x",
     ... "        Element.__init__(self,P)",
     ... "    def __neg__(self):",
+    ... "        return MyBrokenElement(self.parent(),-self.x)",
+    ... "    def _repr_(self):",
+    ... "        return '<%s>'%self.x",
+    ... "    def __hash__(self):",
+    ... "        return hash(self.x)",
+    ... "    def __cmp__(left, right):",
+    ... "        return (<Element>left)._cmp(right)",
+    ... "    def __richcmp__(left, right, op):",
+    ... "        return (<Element>left)._richcmp(right,op)",
+    ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
+    ... "        return cmp(left.x,right.x)",
+    ... "    def raw_test(self):",
+    ... "        return -self",
+    ... "cdef class MyElement(ElementWithCachedMethod):",
+    ... "    cdef public object x",
+    ... "    def __init__(self,P,x):",
+    ... "        self.x=x",
+    ... "        ElementWithCachedMethod.__init__(self,P)",
+    ... "    def __neg__(self):",
     ... "        return MyElement(self.parent(),-self.x)",
     ... "    def _repr_(self):",
     ... "        return '<%s>'%self.x",
     ... "    def __hash__(self):",
     ... "        return hash(self.x)",
-    ... "    def __cmp__(self,other):",
-    ... "        if isinstance(other, MyBrokenElement):",
-    ... "            return cmp(self.x,other.x)",
-    ... "        return -1",
+    ... "    def __cmp__(left, right):",
+    ... "        return (<Element>left)._cmp(right)",
+    ... "    def __richcmp__(left, right, op):",
+    ... "        return (<Element>left)._richcmp(right,op)",
+    ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
+    ... "        return cmp(left.x,right.x)",
     ... "    def raw_test(self):",
     ... "        return -self",
-    ... "cdef class MyElement(MyBrokenElement):",
-    ... "    cdef public dict __cached_methods",
     ... "from sage.structure.parent cimport Parent",
     ... "cdef class MyParent(Parent):",
     ... "    Element = MyElement"]
@@ -139,7 +170,7 @@
     sage: P.invert(e) is P.invert(e)
     True
 
-The cached methods inherited by the element with ``__cached_method`` works::
+The cached methods inherited by ``MyElement`` works::
 
     sage: e.element_cache_test()
     <-5>
diff --git a/sage/misc/lazy_attribute.py b/sage/misc/lazy_attribute.py
--- a/sage/misc/lazy_attribute.py
+++ b/sage/misc/lazy_attribute.py
@@ -518,11 +518,14 @@
         """
         if a is None: # when doing cls.x for cls a class and x a lazy attribute
             return self
-        if hasattr(a.__class__, '__cached_methods'):
+        try:
+            # __cached_methods is supposed to be a public Cython attribute.
+            # Apparently, these are *not* subject to name mangling.
             CM = getattr(a, '__cached_methods')
             if CM is None:
-                CM = a.__cached_methods = {}
-        else:
+                CM = {}
+                setattr(a, '__cached_methods', CM)
+        except AttributeError,msg:
             CM = None
         if CM is not None:
             try:
diff --git a/sage/structure/element.pxd b/sage/structure/element.pxd
--- a/sage/structure/element.pxd
+++ b/sage/structure/element.pxd
@@ -23,7 +23,9 @@
     cpdef _act_on_(self, x, bint self_on_left)
     cpdef _acted_upon_(self, x, bint self_on_left)
     
-    
+cdef class ElementWithCachedMethod(Element):
+    cdef public dict __cached_methods
+
 cdef class ModuleElement(Element)       # forward declaration
 
 cdef class RingElement(ModuleElement)   # forward declaration
diff --git a/sage/structure/element.pyx b/sage/structure/element.pyx
--- a/sage/structure/element.pyx
+++ b/sage/structure/element.pyx
@@ -50,6 +50,7 @@
 
             MonoidElement
                 MultiplicativeGroupElement
+        ElementWithCachedMethod
 
 
 How to Define a New Element Class
@@ -944,12 +945,224 @@
     """
     return IS_INSTANCE(x, ModuleElement)
 
+cdef class ElementWithCachedMethod(Element):
+    r"""
+    An element class that fully supports cached methods.
+
+    NOTE:
+
+    The :class:`~sage.misc.cachefunc.cached_method` decorator provides
+    a convenient way to automatically cache the result of a computation.
+    Since trac ticket #11115, the cached method decorator applied to a
+    method without optional arguments is faster than a hand-written cache
+    in Python, and a cached method without any arguments (except ``self``)
+    is actually faster than a Python method that does nothing more but
+    to return ``1``. A cached method can also be inherited from the parent
+    or element class of a category.
+
+    However, this holds true only if attribute assignment is supported.
+    If you write an extension class in Cython that does not accept attribute
+    assignment then a cached method inherited from the category will be
+    slower (for :class:`~sage.structure.parent.Parent`) or the cache would
+    even break (for :class:`Element`).
+
+    This class should be used if you write an element class, can not provide
+    it with attribute assignment, but want that it inherits a cached method
+    from the category. Under these conditions, your class should inherit
+    from this class rather than :class:`Element`. Then, the cache will work,
+    but certainly slower than with attribute assignment. Lazy attributes
+    work as well.
+
+    EXAMPLE:
+
+    We define three element extension classes. The first inherits from
+    :class:`Element`, the second from this class, and the third simply
+    is a Python class. We also define a parent class and, in Python, a
+    category whose element and parent classes define cached methods.
+    ::
+
+        sage: cython_code = ["from sage.structure.element cimport Element, ElementWithCachedMethod",
+        ... "cdef class MyBrokenElement(Element):",
+        ... "    cdef public object x",
+        ... "    def __init__(self,P,x):",
+        ... "        self.x=x",
+        ... "        Element.__init__(self,P)",
+        ... "    def __neg__(self):",
+        ... "        return MyBrokenElement(self.parent(),-self.x)",
+        ... "    def _repr_(self):",
+        ... "        return '<%s>'%self.x",
+        ... "    def __hash__(self):",
+        ... "        return hash(self.x)",
+        ... "    def __cmp__(left, right):",
+        ... "        return (<Element>left)._cmp(right)",
+        ... "    def __richcmp__(left, right, op):",
+        ... "        return (<Element>left)._richcmp(right,op)",
+        ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
+        ... "        return cmp(left.x,right.x)",
+        ... "    def raw_test(self):",
+        ... "        return -self",
+        ... "cdef class MyElement(ElementWithCachedMethod):",
+        ... "    cdef public object x",
+        ... "    def __init__(self,P,x):",
+        ... "        self.x=x",
+        ... "        Element.__init__(self,P)",
+        ... "    def __neg__(self):",
+        ... "        return MyElement(self.parent(),-self.x)",
+        ... "    def _repr_(self):",
+        ... "        return '<%s>'%self.x",
+        ... "    def __hash__(self):",
+        ... "        return hash(self.x)",
+        ... "    def __cmp__(left, right):",
+        ... "        return (<Element>left)._cmp(right)",
+        ... "    def __richcmp__(left, right, op):",
+        ... "        return (<Element>left)._richcmp(right,op)",
+        ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
+        ... "        return cmp(left.x,right.x)",
+        ... "    def raw_test(self):",
+        ... "        return -self",
+        ... "class MyPythonElement(MyBrokenElement): pass",
+        ... "from sage.structure.parent cimport Parent",
+        ... "cdef class MyParent(Parent):",
+        ... "    Element = MyElement"]
+        sage: cython('\n'.join(cython_code))
+        sage: cython_code = ["from sage.all import cached_method, cached_in_parent_method, Category",
+        ... "class MyCategory(Category):",
+        ... "    @cached_method",
+        ... "    def super_categories(self):",
+        ... "        return [Objects()]",
+        ... "    class ElementMethods:",
+        ... "        @cached_method",
+        ... "        def element_cache_test(self):",
+        ... "            return -self",
+        ... "        @cached_in_parent_method",
+        ... "        def element_via_parent_test(self):",
+        ... "            return -self",
+        ... "    class ParentMethods:",
+        ... "        @cached_method",
+        ... "        def one(self):",
+        ... "            return self.element_class(self,1)",
+        ... "        @cached_method",
+        ... "        def invert(self, x):",
+        ... "            return -x"]
+        sage: cython('\n'.join(cython_code))
+        sage: C = MyCategory()
+        sage: P = MyParent(category=C)
+        sage: ebroken = MyBrokenElement(P,5)
+        sage: e = MyElement(P,5)
+
+    The cached methods inherited by ``MyElement`` works::
+
+        sage: e.element_cache_test()
+        <-5>
+        sage: e.element_cache_test() is e.element_cache_test()
+        True
+        sage: e.element_via_parent_test()
+        <-5>
+        sage: e.element_via_parent_test() is e.element_via_parent_test()
+        True
+
+    The other element class can only inherit a
+    ``cached_in_parent_method``, since the cache is stored in the
+    parent. In fact, equal elements share the cache, even if they are
+    of different types::
+
+        sage: e == ebroken
+        True
+        sage: type(e) == type(ebroken)
+        False
+        sage: ebroken.element_via_parent_test() is e.element_via_parent_test()
+        True
+
+    However, the cache of the other inherited method breaks, although the method
+    as such works::
+
+        sage: ebroken.element_cache_test()
+        <-5>
+        sage: ebroken.element_cache_test() is ebroken.element_cache_test()
+        False
+
+    Since ``e`` and ``ebroken`` share the cache, when we empty it for one element
+    it is empty for the other as well::
+
+        sage: b = ebroken.element_via_parent_test()
+        sage: e.element_via_parent_test.clear_cache()
+        sage: b is ebroken.element_via_parent_test()
+        False
+
+    Note that the cache only breaks for elements that do no allow attribute assignment.
+    A Python version of ``MyBrokenElement`` therefore allows for cached methods::
+
+        sage: epython = MyPythonElement(P,5)
+        sage: epython.element_cache_test()
+        <-5>
+        sage: epython.element_cache_test() is epython.element_cache_test()
+        True
+
+    """
+    def __getattr__(self, name):
+        """
+        This getattr method ensures that cached methods and lazy attributes
+        can be inherited from the element class of a category.
+
+        NOTE:
+
+        The use of cached methods is demonstrated in the main doc string of
+        this class. Here, we demonstrate lazy attributes.
+
+        EXAMPLE::
+
+            sage: cython_code = ["from sage.structure.element cimport ElementWithCachedMethod",
+            ... "cdef class MyElement(ElementWithCachedMethod):",
+            ... "    cdef public object x",
+            ... "    def __init__(self,P,x):",
+            ... "        self.x=x",
+            ... "        ElementWithCachedMethod.__init__(self,P)",
+            ... "    def _repr_(self):",
+            ... "        return '<%s>'%self.x",
+            ... "from sage.structure.parent cimport Parent",
+            ... "cdef class MyParent(Parent):",
+            ... "    Element = MyElement",
+            ... "from sage.all import cached_method, lazy_attribute, Category",
+            ... "class MyCategory(Category):",
+            ... "    @cached_method",
+            ... "    def super_categories(self):",
+            ... "        return [Objects()]",
+            ... "    class ElementMethods:",
+            ... "        @lazy_attribute",
+            ... "        def my_lazy_attr(self):",
+            ... "            return 'lazy attribute of <%d>'%self.x"]
+            sage: cython('\n'.join(cython_code))
+            sage: C = MyCategory()
+            sage: P = MyParent(category=C)
+            sage: e = MyElement(P,5)
+            sage: e.my_lazy_attr
+            'lazy attribute of <5>'
+            sage: e.my_lazy_attr is e.my_lazy_attr
+            True
+
+        """
+        if name.startswith('__') and not name.endswith('_'):
+            raise AttributeError, AttributeErrorMessage(self, name)
+        try:
+            return self.__cached_methods[name]
+        except KeyError:
+            attr = getattr_from_other_class(self,
+                                        self._parent.category().element_class,
+                                        name)
+            self.__cached_methods[name] = attr
+            return attr
+        except TypeError:
+            attr = getattr_from_other_class(self,
+                                        self._parent.category().element_class,
+                                        name)
+            self.__cached_methods = {name : attr}
+            return attr
 
 cdef class ModuleElement(Element):
     """
     Generic element of a module.
     """
-    
+
     ##################################################
     # Addition
     ##################################################    
diff --git a/sage/structure/parent.pyx b/sage/structure/parent.pyx
--- a/sage/structure/parent.pyx
+++ b/sage/structure/parent.pyx
@@ -783,17 +783,18 @@
             AttributeError: 'sage.structure.parent.Parent' object has no attribute '__foo'
 
         """
+        if (name.startswith('__') and not name.endswith('_')) or self._category is None:
+            raise AttributeError, AttributeErrorMessage(self, name)
         try:
             return self.__cached_methods[name]
-        except (KeyError,TypeError):
-            pass
-        if (name.startswith('__') and not name.endswith('_')) or self._category is None:
-            raise AttributeError, AttributeErrorMessage(self, name)
-        if self.__cached_methods is None:
-            self.__cached_methods  = {}
-        attr = getattr_from_other_class(self, self._category.parent_class, name)
-        self.__cached_methods[name] = attr
-        return attr
+        except KeyError:
+            attr = getattr_from_other_class(self, self._category.parent_class, name)
+            self.__cached_methods[name] = attr
+            return attr
+        except TypeError:
+            attr = getattr_from_other_class(self, self._category.parent_class, name)
+            self.__cached_methods = {name:attr}
+            return attr
 
     def __dir__(self):
         """
