| 948 | cdef class ElementWithCachedMethod(Element): |
| 949 | r""" |
| 950 | An element class that fully supports cached methods. |
| 951 | |
| 952 | NOTE: |
| 953 | |
| 954 | The :class:`~sage.misc.cachefunc.cached_method` decorator provides |
| 955 | a convenient way to automatically cache the result of a computation. |
| 956 | Since trac ticket #11115, the cached method decorator applied to a |
| 957 | method without optional arguments is faster than a hand-written cache |
| 958 | in Python, and a cached method without any arguments (except ``self``) |
| 959 | is actually faster than a Python method that does nothing more but |
| 960 | to return ``1``. A cached method can also be inherited from the parent |
| 961 | or element class of a category. |
| 962 | |
| 963 | However, this holds true only if attribute assignment is supported. |
| 964 | If you write an extension class in Cython that does not accept attribute |
| 965 | assignment then a cached method inherited from the category will be |
| 966 | slower (for :class:`~sage.structure.parent.Parent`) or the cache would |
| 967 | even break (for :class:`Element`). |
| 968 | |
| 969 | This class should be used if you write an element class, can not provide |
| 970 | it with attribute assignment, but want that it inherits a cached method |
| 971 | from the category. Under these conditions, your class should inherit |
| 972 | from this class rather than :class:`Element`. Then, the cache will work, |
| 973 | but certainly slower than with attribute assignment. Lazy attributes |
| 974 | work as well. |
| 975 | |
| 976 | EXAMPLE: |
| 977 | |
| 978 | We define three element extension classes. The first inherits from |
| 979 | :class:`Element`, the second from this class, and the third simply |
| 980 | is a Python class. We also define a parent class and, in Python, a |
| 981 | category whose element and parent classes define cached methods. |
| 982 | :: |
| 983 | |
| 984 | sage: cython_code = ["from sage.structure.element cimport Element, ElementWithCachedMethod", |
| 985 | ... "cdef class MyBrokenElement(Element):", |
| 986 | ... " cdef public object x", |
| 987 | ... " def __init__(self,P,x):", |
| 988 | ... " self.x=x", |
| 989 | ... " Element.__init__(self,P)", |
| 990 | ... " def __neg__(self):", |
| 991 | ... " return MyBrokenElement(self.parent(),-self.x)", |
| 992 | ... " def _repr_(self):", |
| 993 | ... " return '<%s>'%self.x", |
| 994 | ... " def __hash__(self):", |
| 995 | ... " return hash(self.x)", |
| 996 | ... " def __cmp__(left, right):", |
| 997 | ... " return (<Element>left)._cmp(right)", |
| 998 | ... " def __richcmp__(left, right, op):", |
| 999 | ... " return (<Element>left)._richcmp(right,op)", |
| 1000 | ... " cdef int _cmp_c_impl(left, Element right) except -2:", |
| 1001 | ... " return cmp(left.x,right.x)", |
| 1002 | ... " def raw_test(self):", |
| 1003 | ... " return -self", |
| 1004 | ... "cdef class MyElement(ElementWithCachedMethod):", |
| 1005 | ... " cdef public object x", |
| 1006 | ... " def __init__(self,P,x):", |
| 1007 | ... " self.x=x", |
| 1008 | ... " Element.__init__(self,P)", |
| 1009 | ... " def __neg__(self):", |
| 1010 | ... " return MyElement(self.parent(),-self.x)", |
| 1011 | ... " def _repr_(self):", |
| 1012 | ... " return '<%s>'%self.x", |
| 1013 | ... " def __hash__(self):", |
| 1014 | ... " return hash(self.x)", |
| 1015 | ... " def __cmp__(left, right):", |
| 1016 | ... " return (<Element>left)._cmp(right)", |
| 1017 | ... " def __richcmp__(left, right, op):", |
| 1018 | ... " return (<Element>left)._richcmp(right,op)", |
| 1019 | ... " cdef int _cmp_c_impl(left, Element right) except -2:", |
| 1020 | ... " return cmp(left.x,right.x)", |
| 1021 | ... " def raw_test(self):", |
| 1022 | ... " return -self", |
| 1023 | ... "class MyPythonElement(MyBrokenElement): pass", |
| 1024 | ... "from sage.structure.parent cimport Parent", |
| 1025 | ... "cdef class MyParent(Parent):", |
| 1026 | ... " Element = MyElement"] |
| 1027 | sage: cython('\n'.join(cython_code)) |
| 1028 | sage: cython_code = ["from sage.all import cached_method, cached_in_parent_method, Category", |
| 1029 | ... "class MyCategory(Category):", |
| 1030 | ... " @cached_method", |
| 1031 | ... " def super_categories(self):", |
| 1032 | ... " return [Objects()]", |
| 1033 | ... " class ElementMethods:", |
| 1034 | ... " @cached_method", |
| 1035 | ... " def element_cache_test(self):", |
| 1036 | ... " return -self", |
| 1037 | ... " @cached_in_parent_method", |
| 1038 | ... " def element_via_parent_test(self):", |
| 1039 | ... " return -self", |
| 1040 | ... " class ParentMethods:", |
| 1041 | ... " @cached_method", |
| 1042 | ... " def one(self):", |
| 1043 | ... " return self.element_class(self,1)", |
| 1044 | ... " @cached_method", |
| 1045 | ... " def invert(self, x):", |
| 1046 | ... " return -x"] |
| 1047 | sage: cython('\n'.join(cython_code)) |
| 1048 | sage: C = MyCategory() |
| 1049 | sage: P = MyParent(category=C) |
| 1050 | sage: ebroken = MyBrokenElement(P,5) |
| 1051 | sage: e = MyElement(P,5) |
| 1052 | |
| 1053 | The cached methods inherited by ``MyElement`` works:: |
| 1054 | |
| 1055 | sage: e.element_cache_test() |
| 1056 | <-5> |
| 1057 | sage: e.element_cache_test() is e.element_cache_test() |
| 1058 | True |
| 1059 | sage: e.element_via_parent_test() |
| 1060 | <-5> |
| 1061 | sage: e.element_via_parent_test() is e.element_via_parent_test() |
| 1062 | True |
| 1063 | |
| 1064 | The other element class can only inherit a |
| 1065 | ``cached_in_parent_method``, since the cache is stored in the |
| 1066 | parent. In fact, equal elements share the cache, even if they are |
| 1067 | of different types:: |
| 1068 | |
| 1069 | sage: e == ebroken |
| 1070 | True |
| 1071 | sage: type(e) == type(ebroken) |
| 1072 | False |
| 1073 | sage: ebroken.element_via_parent_test() is e.element_via_parent_test() |
| 1074 | True |
| 1075 | |
| 1076 | However, the cache of the other inherited method breaks, although the method |
| 1077 | as such works:: |
| 1078 | |
| 1079 | sage: ebroken.element_cache_test() |
| 1080 | <-5> |
| 1081 | sage: ebroken.element_cache_test() is ebroken.element_cache_test() |
| 1082 | False |
| 1083 | |
| 1084 | Since ``e`` and ``ebroken`` share the cache, when we empty it for one element |
| 1085 | it is empty for the other as well:: |
| 1086 | |
| 1087 | sage: b = ebroken.element_via_parent_test() |
| 1088 | sage: e.element_via_parent_test.clear_cache() |
| 1089 | sage: b is ebroken.element_via_parent_test() |
| 1090 | False |
| 1091 | |
| 1092 | Note that the cache only breaks for elements that do no allow attribute assignment. |
| 1093 | A Python version of ``MyBrokenElement`` therefore allows for cached methods:: |
| 1094 | |
| 1095 | sage: epython = MyPythonElement(P,5) |
| 1096 | sage: epython.element_cache_test() |
| 1097 | <-5> |
| 1098 | sage: epython.element_cache_test() is epython.element_cache_test() |
| 1099 | True |
| 1100 | |
| 1101 | """ |
| 1102 | def __getattr__(self, name): |
| 1103 | """ |
| 1104 | This getattr method ensures that cached methods and lazy attributes |
| 1105 | can be inherited from the element class of a category. |
| 1106 | |
| 1107 | NOTE: |
| 1108 | |
| 1109 | The use of cached methods is demonstrated in the main doc string of |
| 1110 | this class. Here, we demonstrate lazy attributes. |
| 1111 | |
| 1112 | EXAMPLE:: |
| 1113 | |
| 1114 | sage: cython_code = ["from sage.structure.element cimport ElementWithCachedMethod", |
| 1115 | ... "cdef class MyElement(ElementWithCachedMethod):", |
| 1116 | ... " cdef public object x", |
| 1117 | ... " def __init__(self,P,x):", |
| 1118 | ... " self.x=x", |
| 1119 | ... " ElementWithCachedMethod.__init__(self,P)", |
| 1120 | ... " def _repr_(self):", |
| 1121 | ... " return '<%s>'%self.x", |
| 1122 | ... "from sage.structure.parent cimport Parent", |
| 1123 | ... "cdef class MyParent(Parent):", |
| 1124 | ... " Element = MyElement", |
| 1125 | ... "from sage.all import cached_method, lazy_attribute, Category", |
| 1126 | ... "class MyCategory(Category):", |
| 1127 | ... " @cached_method", |
| 1128 | ... " def super_categories(self):", |
| 1129 | ... " return [Objects()]", |
| 1130 | ... " class ElementMethods:", |
| 1131 | ... " @lazy_attribute", |
| 1132 | ... " def my_lazy_attr(self):", |
| 1133 | ... " return 'lazy attribute of <%d>'%self.x"] |
| 1134 | sage: cython('\n'.join(cython_code)) |
| 1135 | sage: C = MyCategory() |
| 1136 | sage: P = MyParent(category=C) |
| 1137 | sage: e = MyElement(P,5) |
| 1138 | sage: e.my_lazy_attr |
| 1139 | 'lazy attribute of <5>' |
| 1140 | sage: e.my_lazy_attr is e.my_lazy_attr |
| 1141 | True |
| 1142 | |
| 1143 | """ |
| 1144 | if name.startswith('__') and not name.endswith('_'): |
| 1145 | raise AttributeError, AttributeErrorMessage(self, name) |
| 1146 | try: |
| 1147 | return self.__cached_methods[name] |
| 1148 | except KeyError: |
| 1149 | attr = getattr_from_other_class(self, |
| 1150 | self._parent.category().element_class, |
| 1151 | name) |
| 1152 | self.__cached_methods[name] = attr |
| 1153 | return attr |
| 1154 | except TypeError: |
| 1155 | attr = getattr_from_other_class(self, |
| 1156 | self._parent.category().element_class, |
| 1157 | name) |
| 1158 | self.__cached_methods = {name : attr} |
| 1159 | return attr |