| | 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 |