| | 1 | r""" |
| | 2 | Cached Functions and Methods |
| | 3 | |
| | 4 | AUTHORS: |
| | 5 | |
| | 6 | - William Stein (inspired by conversation with Justin Walker). |
| | 7 | - Mike Hansen (added doctests and made it work with class methods). |
| | 8 | - Willem Jan Palenstijn (add CachedMethodCaller for binding cached |
| | 9 | methods to instances). |
| | 10 | - Tom Boothby (added DiskCachedFunction). |
| | 11 | - Simon King (improved performance, more doctests, cython version, |
| | 12 | added CachedMethodCallerNoArgs). |
| | 13 | |
| | 14 | EXAMPLES: |
| | 15 | |
| | 16 | By trac ticket #11115, cached functions and methods are now also |
| | 17 | available in Cython code. The following examples cover various ways |
| | 18 | of usage. |
| | 19 | |
| | 20 | Python functions:: |
| | 21 | |
| | 22 | sage: @cached_function |
| | 23 | ... def test_pfunc(x): |
| | 24 | ... ''' |
| | 25 | ... Some documentation |
| | 26 | ... ''' |
| | 27 | ... return -x |
| | 28 | ... |
| | 29 | sage: test_pfunc(5) is test_pfunc(5) |
| | 30 | True |
| | 31 | |
| | 32 | Unfortunately, cython functions do not allow arbitrary |
| | 33 | decorators. However, one can wrap a Cython function and |
| | 34 | turn it into a cached function, by trac ticket #11115. |
| | 35 | We need to provide the name that the wrapped method or |
| | 36 | function should have, since otherwise the name of the |
| | 37 | original function would be used:: |
| | 38 | |
| | 39 | sage: cython('''cpdef test_funct(x): return -x''') |
| | 40 | sage: wrapped_funct = cached_function(test_funct, name='wrapped_funct') |
| | 41 | sage: wrapped_funct |
| | 42 | Cached version of <built-in function test_funct> |
| | 43 | sage: wrapped_funct.__name__ |
| | 44 | 'wrapped_funct' |
| | 45 | sage: wrapped_funct(5) |
| | 46 | -5 |
| | 47 | sage: wrapped_funct(5) is wrapped_funct(5) |
| | 48 | True |
| | 49 | |
| | 50 | We can proceed similarly for cached methods of Cython classes, |
| | 51 | provided that they allow attribute assignment or have a public |
| | 52 | attribute ``__cached_methods`` of type ``<dict>``. Since trac ticket |
| | 53 | #11115, this is the case for all classes inheriting from |
| | 54 | :class:`~sage.structure.parent.Parent`:: |
| | 55 | |
| | 56 | sage: cython_code = ['cpdef test_meth(self,x):', |
| | 57 | ... ' "some doc for a wrapped cython method"', |
| | 58 | ... ' return -x', |
| | 59 | ... 'from sage.all import cached_method', |
| | 60 | ... 'from sage.structure.parent cimport Parent', |
| | 61 | ... 'cdef class MyClass(Parent):', |
| | 62 | ... ' wrapped_method = cached_method(test_meth,name="wrapped_method")'] |
| | 63 | sage: cython('\n'.join(cython_code)) |
| | 64 | sage: O = MyClass() |
| | 65 | sage: O.wrapped_method |
| | 66 | Cached version of <built-in function test_meth> |
| | 67 | sage: O.wrapped_method.__name__ |
| | 68 | 'wrapped_method' |
| | 69 | sage: O.wrapped_method(5) |
| | 70 | -5 |
| | 71 | sage: O.wrapped_method(5) is O.wrapped_method(5) |
| | 72 | True |
| | 73 | |
| | 74 | By trac ticket #11115, even if a parent does not allow attribute |
| | 75 | assignment, it can inherit a cached method from the parent class of a |
| | 76 | category (previously, the cache would have been broken):: |
| | 77 | |
| | 78 | sage: cython_code = ["from sage.all import cached_method, cached_in_parent_method, Category", |
| | 79 | ... "class MyCategory(Category):", |
| | 80 | ... " @cached_method", |
| | 81 | ... " def super_categories(self):", |
| | 82 | ... " return [Objects()]", |
| | 83 | ... " class ElementMethods:", |
| | 84 | ... " @cached_method", |
| | 85 | ... " def element_cache_test(self):", |
| | 86 | ... " return -self", |
| | 87 | ... " @cached_in_parent_method", |
| | 88 | ... " def element_via_parent_test(self):", |
| | 89 | ... " return -self", |
| | 90 | ... " class ParentMethods:", |
| | 91 | ... " @cached_method", |
| | 92 | ... " def one(self):", |
| | 93 | ... " return self.element_class(self,1)", |
| | 94 | ... " @cached_method", |
| | 95 | ... " def invert(self, x):", |
| | 96 | ... " return -x"] |
| | 97 | sage: cython('\n'.join(cython_code)) |
| | 98 | sage: C = MyCategory() |
| | 99 | |
| | 100 | To make cacheing work on the elements, we need to provide it with |
| | 101 | a public attribute ``__cached_methods``, as in the second class below:: |
| | 102 | |
| | 103 | sage: cython_code = ["from sage.structure.element cimport Element", |
| | 104 | ... "cdef class MyBrokenElement(Element):", |
| | 105 | ... " cdef public object x", |
| | 106 | ... " def __init__(self,P,x):", |
| | 107 | ... " self.x=x", |
| | 108 | ... " Element.__init__(self,P)", |
| | 109 | ... " def __neg__(self):", |
| | 110 | ... " return MyElement(self.parent(),-self.x)", |
| | 111 | ... " def _repr_(self):", |
| | 112 | ... " return '<%s>'%self.x", |
| | 113 | ... " def __hash__(self):", |
| | 114 | ... " return hash(self.x)", |
| | 115 | ... " def __cmp__(self,other):", |
| | 116 | ... " if isinstance(other, MyBrokenElement):", |
| | 117 | ... " return cmp(self.x,other.x)", |
| | 118 | ... " return -1", |
| | 119 | ... " def raw_test(self):", |
| | 120 | ... " return -self", |
| | 121 | ... "cdef class MyElement(MyBrokenElement):", |
| | 122 | ... " cdef public dict __cached_methods", |
| | 123 | ... "from sage.structure.parent cimport Parent", |
| | 124 | ... "cdef class MyParent(Parent):", |
| | 125 | ... " Element = MyElement"] |
| | 126 | sage: cython('\n'.join(cython_code)) |
| | 127 | sage: P = MyParent(category=C) |
| | 128 | sage: ebroken = MyBrokenElement(P,5) |
| | 129 | sage: e = MyElement(P,5) |
| | 130 | |
| | 131 | The cached methods inherited by the parent works:: |
| | 132 | |
| | 133 | sage: P.one() |
| | 134 | <1> |
| | 135 | sage: P.one() is P.one() |
| | 136 | True |
| | 137 | sage: P.invert(e) |
| | 138 | <-5> |
| | 139 | sage: P.invert(e) is P.invert(e) |
| | 140 | True |
| | 141 | |
| | 142 | The cached methods inherited by the element with ``__cached_method`` works:: |
| | 143 | |
| | 144 | sage: e.element_cache_test() |
| | 145 | <-5> |
| | 146 | sage: e.element_cache_test() is e.element_cache_test() |
| | 147 | True |
| | 148 | sage: e.element_via_parent_test() |
| | 149 | <-5> |
| | 150 | sage: e.element_via_parent_test() is e.element_via_parent_test() |
| | 151 | True |
| | 152 | |
| | 153 | The other element class can only inherit a `cached_in_parent_method`, since |
| | 154 | the cache is stored in the parent. In fact, equal elements share the cache, |
| | 155 | even if they are of different types:: |
| | 156 | |
| | 157 | sage: e == ebroken |
| | 158 | True |
| | 159 | sage: type(e) == type(ebroken) |
| | 160 | False |
| | 161 | sage: ebroken.element_via_parent_test() is e.element_via_parent_test() |
| | 162 | True |
| | 163 | |
| | 164 | However, the cache of the other inherited method breaks, although the method |
| | 165 | as such works:: |
| | 166 | |
| | 167 | sage: ebroken.element_cache_test() |
| | 168 | <-5> |
| | 169 | sage: ebroken.element_cache_test() is ebroken.element_cache_test() |
| | 170 | False |
| | 171 | |
| | 172 | The cache can be emptied:: |
| | 173 | |
| | 174 | sage: a = test_pfunc(5) |
| | 175 | sage: test_pfunc.clear_cache() |
| | 176 | sage: a is test_pfunc(5) |
| | 177 | False |
| | 178 | sage: a = P.one() |
| | 179 | sage: P.one.clear_cache() |
| | 180 | sage: a is P.one() |
| | 181 | False |
| | 182 | |
| | 183 | Since ``e`` and ``ebroken`` share the cache, when we empty it for one element |
| | 184 | it is empty for the other as well:: |
| | 185 | |
| | 186 | sage: b = ebroken.element_via_parent_test() |
| | 187 | sage: e.element_via_parent_test.clear_cache() |
| | 188 | sage: b is ebroken.element_via_parent_test() |
| | 189 | False |
| | 190 | |
| | 191 | Introspection works:: |
| | 192 | |
| | 193 | sage: from sage.misc.edit_module import file_and_line |
| | 194 | sage: from sage.misc.sageinspect import sage_getdoc, sage_getfile, sage_getsource |
| | 195 | sage: print sage_getdoc(test_pfunc) |
| | 196 | Some documentation |
| | 197 | sage: print sage_getdoc(O.wrapped_method) |
| | 198 | File: ... |
| | 199 | (starting at line ...) some doc for a wrapped cython method |
| | 200 | sage: print sage_getsource(O.wrapped_method) |
| | 201 | cpdef test_meth(self,x): |
| | 202 | "some doc for a wrapped cython method" |
| | 203 | return -x |
| | 204 | |
| | 205 | It is a very common special case to cache a method that has no |
| | 206 | arguments. In that special case, the time needed to access the cache |
| | 207 | can be drastically reduced by using a special implmentation. The |
| | 208 | cached method decorator automatically determines which implementation |
| | 209 | ought to be chosen. A typical example is |
| | 210 | :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal.gens` |
| | 211 | (no arguments) versus |
| | 212 | :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal.groebner_basis` |
| | 213 | (several arguments):: |
| | 214 | |
| | 215 | sage: P.<a,b,c,d> = QQ[] |
| | 216 | sage: I = P*[a,b] |
| | 217 | sage: I.gens() |
| | 218 | [a, b] |
| | 219 | sage: I.gens() is I.gens() |
| | 220 | True |
| | 221 | sage: I.groebner_basis() |
| | 222 | [a, b] |
| | 223 | sage: I.groebner_basis() is I.groebner_basis() |
| | 224 | True |
| | 225 | sage: type(I.gens) |
| | 226 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 227 | sage: type(I.groebner_basis) |
| | 228 | <type 'sage.misc.cachefunc.CachedMethodCaller'> |
| | 229 | |
| | 230 | """ |
| | 231 | ######################################################################## |
| | 232 | # Copyright (C) 2008 William Stein <wstein@gmail.com> |
| | 233 | # Mike Hansen <mhansen@gmail.com> |
| | 234 | # |
| | 235 | # Distributed under the terms of the GNU General Public License (GPL) |
| | 236 | # |
| | 237 | # http://www.gnu.org/licenses/ |
| | 238 | ######################################################################## |
| | 239 | from function_mangling import ArgumentFixer |
| | 240 | import os |
| | 241 | from sage.misc.sageinspect import sage_getfile, sage_getsourcelines |
| | 242 | |
| | 243 | cdef class CachedFunction(object): |
| | 244 | """ |
| | 245 | Create a cached version of a function, which only recomputes |
| | 246 | values it hasn't already computed. Synonyme: ``cached_function`` |
| | 247 | |
| | 248 | INPUT: |
| | 249 | |
| | 250 | - ``f`` -- a function |
| | 251 | - ``name`` (optional string) -- name that the cached version |
| | 252 | of ``f`` should be provided with. |
| | 253 | |
| | 254 | If ``f`` is a function, do either ``g = CachedFunction(f)`` |
| | 255 | or ``g = cached_function(f)`` to make a cached version of ``f``, |
| | 256 | or put ``@cached_function`` right before the definition of ``f`` |
| | 257 | (i.e., use Python decorators):: |
| | 258 | |
| | 259 | @cached_function |
| | 260 | def f(...): |
| | 261 | .... |
| | 262 | |
| | 263 | The inputs to the function must be hashable. |
| | 264 | |
| | 265 | EXAMPLES:: |
| | 266 | |
| | 267 | sage: @cached_function |
| | 268 | ... def mul(x, y=2): |
| | 269 | ... return x*y |
| | 270 | ... |
| | 271 | sage: mul(3) |
| | 272 | 6 |
| | 273 | |
| | 274 | We demonstrate that the result is cached, and that, moreover, |
| | 275 | the cache takes into account the various ways of providing |
| | 276 | default arguments:: |
| | 277 | |
| | 278 | sage: mul(3) is mul(3,2) |
| | 279 | True |
| | 280 | sage: mul(3,y=2) is mul(3,2) |
| | 281 | True |
| | 282 | |
| | 283 | The user can clear the cache:: |
| | 284 | |
| | 285 | sage: a = mul(4) |
| | 286 | sage: mul.clear_cache() |
| | 287 | sage: a is mul(4) |
| | 288 | False |
| | 289 | |
| | 290 | It is also possible to explicitly override the cache with |
| | 291 | a different value:: |
| | 292 | |
| | 293 | sage: mul.set_cache('foo',5) |
| | 294 | sage: mul(5,2) |
| | 295 | 'foo' |
| | 296 | |
| | 297 | """ |
| | 298 | def __init__(self, f, classmethod=False, name=None): |
| | 299 | """ |
| | 300 | Create a cached version of a function, which only recomputes |
| | 301 | values it hasn't already computed. A custom name can be |
| | 302 | provided by an optional argument "name". |
| | 303 | |
| | 304 | If f is a function, do either g = CachedFunction(f) to make |
| | 305 | a cached version of f, or put @CachedFunction right before |
| | 306 | the definition of f (i.e., use Python decorators, but then |
| | 307 | the optional argument ``name`` can not be used):: |
| | 308 | |
| | 309 | @CachedFunction |
| | 310 | def f(...): |
| | 311 | .... |
| | 312 | |
| | 313 | The inputs to the function must be hashable. |
| | 314 | |
| | 315 | TESTS:: |
| | 316 | |
| | 317 | sage: g = CachedFunction(number_of_partitions) |
| | 318 | sage: g.__name__ |
| | 319 | 'number_of_partitions' |
| | 320 | sage: 'partitions' in sage.misc.sageinspect.sage_getdoc(g) |
| | 321 | True |
| | 322 | sage: g(5) |
| | 323 | 7 |
| | 324 | sage: g.cache |
| | 325 | {((5, None, 'default'), ()): 7} |
| | 326 | sage: def f(t=1): print(t) |
| | 327 | sage: h = CachedFunction(f) |
| | 328 | sage: w = walltime() |
| | 329 | sage: h(); h(1); h(t=1) |
| | 330 | 1 |
| | 331 | sage: walltime(w) < 2 |
| | 332 | True |
| | 333 | |
| | 334 | """ |
| | 335 | self._common_init(f, ArgumentFixer(f,classmethod=classmethod), name=name) |
| | 336 | self.cache = {} |
| | 337 | |
| | 338 | def _common_init(self, f, argument_fixer, name=None): |
| | 339 | """ |
| | 340 | Perform initialization common to CachedFunction and CachedMethodCaller. |
| | 341 | |
| | 342 | TESTS:: |
| | 343 | |
| | 344 | sage: @cached_function |
| | 345 | ... def test_cache(x): |
| | 346 | ... return -x |
| | 347 | sage: test_cache._fix_to_pos is not None # indirect doctest |
| | 348 | True |
| | 349 | |
| | 350 | """ |
| | 351 | self.f = f |
| | 352 | if name is not None: |
| | 353 | self.__name__ = name |
| | 354 | elif hasattr(f, "func_name"): |
| | 355 | self.__name__ = f.func_name |
| | 356 | else: |
| | 357 | self.__name__ = f.__name__ |
| | 358 | self.__module__ = f.__module__ |
| | 359 | if argument_fixer is not None: # it is None for CachedMethodCallerNoArgs |
| | 360 | self._argument_fixer = argument_fixer |
| | 361 | self._fix_to_pos = argument_fixer.fix_to_pos |
| | 362 | |
| | 363 | ######### |
| | 364 | ## Introspection |
| | 365 | ## |
| | 366 | ## We provide some methods explicitly, and |
| | 367 | ## forward other questions to the cached function. |
| | 368 | |
| | 369 | def _sage_doc_(self): |
| | 370 | """ |
| | 371 | Provide documentation for the cached function. |
| | 372 | |
| | 373 | A cached function shall inherit the documentation |
| | 374 | from the function that is wrapped, not from the |
| | 375 | documentation of the wrapper. |
| | 376 | |
| | 377 | TEST:: |
| | 378 | |
| | 379 | sage: P.<x,y> = QQ[] |
| | 380 | sage: I = P*[x,y] |
| | 381 | sage: from sage.misc.sageinspect import sage_getdoc |
| | 382 | sage: print sage_getdoc(I.groebner_basis) # indirect doctest |
| | 383 | File: sage/rings/polynomial/multi_polynomial_ideal.py (starting at |
| | 384 | line ...) |
| | 385 | ... |
| | 386 | ALGORITHM: Uses Singular, Magma (if available), Macaulay2 (if |
| | 387 | available), or a toy implementation. |
| | 388 | |
| | 389 | """ |
| | 390 | f = self.f |
| | 391 | if hasattr(f, "func_doc"): |
| | 392 | try: |
| | 393 | sourcelines = sage_getsourcelines(f) |
| | 394 | except IOError: |
| | 395 | sourcelines = None |
| | 396 | if sourcelines is not None: |
| | 397 | import os |
| | 398 | SAGE_ROOT = os.environ['SAGE_ROOT'] |
| | 399 | filename = sage_getfile(f) |
| | 400 | # The following is a heuristics to get |
| | 401 | # the file name of the cached function |
| | 402 | # or method |
| | 403 | if filename.startswith(SAGE_ROOT+'/devel/sage/'): |
| | 404 | filename = filename[len(SAGE_ROOT+'/devel/sage/'):] |
| | 405 | elif 'site-packages/' in filename: |
| | 406 | filename = filename.split('site-packages/',1)[1] |
| | 407 | file_info = "File: %s (starting at line %d)"%(filename,sourcelines[1]) |
| | 408 | doc = file_info+(f.func_doc or '') |
| | 409 | else: |
| | 410 | doc = f.func_doc |
| | 411 | else: |
| | 412 | doc = f.__doc__ |
| | 413 | return doc |
| | 414 | |
| | 415 | def _sage_src_(self): |
| | 416 | """ |
| | 417 | Returns the source code for the wrapped function. |
| | 418 | |
| | 419 | TESTS:: |
| | 420 | |
| | 421 | sage: from sage.misc.sageinspect import sage_getsource |
| | 422 | sage: g = CachedFunction(number_of_partitions) |
| | 423 | sage: 'bober' in sage_getsource(g) # indirect doctest |
| | 424 | True |
| | 425 | |
| | 426 | """ |
| | 427 | from sage.misc.sageinspect import sage_getsource |
| | 428 | return sage_getsource(self.f) |
| | 429 | |
| | 430 | def _sage_src_lines_(self): |
| | 431 | r""" |
| | 432 | Returns the list of source lines and the first line number |
| | 433 | of the wrapped function. |
| | 434 | |
| | 435 | TEST:: |
| | 436 | |
| | 437 | sage: P.<x,y> = QQ[] |
| | 438 | sage: I = P*[x,y] |
| | 439 | sage: from sage.misc.sageinspect import sage_getsourcelines |
| | 440 | sage: l = " elif algorithm == 'macaulay2:gb':\n" |
| | 441 | sage: l in sage_getsourcelines(I.groebner_basis)[0] # indirect doctest |
| | 442 | True |
| | 443 | |
| | 444 | """ |
| | 445 | from sage.misc.sageinspect import sage_getsourcelines |
| | 446 | return sage_getsourcelines(self.f) |
| | 447 | |
| | 448 | def _sage_argspec_(self): |
| | 449 | """ |
| | 450 | Return the argspec of the wrapped function or method. |
| | 451 | |
| | 452 | This was implemented in trac ticket #11115. |
| | 453 | |
| | 454 | EXAMPLE:: |
| | 455 | |
| | 456 | sage: P.<x,y> = QQ[] |
| | 457 | sage: I = P*[x,y] |
| | 458 | sage: from sage.misc.sageinspect import sage_getargspec |
| | 459 | sage: sage_getargspec(I.groebner_basis) # indirect doctest |
| | 460 | ArgSpec(args=['self', 'algorithm', 'deg_bound', 'mult_bound', 'prot'], |
| | 461 | varargs='args', keywords='kwds', defaults=('', None, None, |
| | 462 | False)) |
| | 463 | |
| | 464 | """ |
| | 465 | from sage.misc.sageinspect import sage_getargspec |
| | 466 | return sage_getargspec(self.f) |
| | 467 | |
| | 468 | def __call__(self, *args, **kwds): |
| | 469 | """ |
| | 470 | Return value from cache or call the wrapped function, |
| | 471 | caching the output. |
| | 472 | |
| | 473 | TESTS:: |
| | 474 | |
| | 475 | sage: g = CachedFunction(number_of_partitions) |
| | 476 | sage: a = g(5) |
| | 477 | sage: g.get_cache() |
| | 478 | {((5, None, 'default'), ()): 7} |
| | 479 | sage: a = g(10^5) # indirect doctest |
| | 480 | sage: a == number_of_partitions(10^5) |
| | 481 | True |
| | 482 | sage: a is g(10^5) |
| | 483 | True |
| | 484 | sage: a is number_of_partitions(10^5) |
| | 485 | False |
| | 486 | |
| | 487 | """ |
| | 488 | # We shortcut a common case of no arguments |
| | 489 | if args or kwds: |
| | 490 | k = self._fix_to_pos(*args, **kwds) |
| | 491 | else: |
| | 492 | if self._default_key is not None: |
| | 493 | k = self._default_key |
| | 494 | else: |
| | 495 | k = self._default_key = self._fix_to_pos() |
| | 496 | |
| | 497 | try: |
| | 498 | return (<dict>self.cache)[k] |
| | 499 | except KeyError: |
| | 500 | w = self.f(*args, **kwds) |
| | 501 | self.cache[k] = w |
| | 502 | return w |
| | 503 | |
| | 504 | cpdef get_cache(self): |
| | 505 | """ |
| | 506 | Returns the cache dictionary. |
| | 507 | |
| | 508 | EXAMPLES:: |
| | 509 | |
| | 510 | sage: g = CachedFunction(number_of_partitions) |
| | 511 | sage: a = g(5) |
| | 512 | sage: g.get_cache() |
| | 513 | {((5, None, 'default'), ()): 7} |
| | 514 | |
| | 515 | """ |
| | 516 | return self.cache |
| | 517 | |
| | 518 | def is_in_cache(self, *args, **kwds): |
| | 519 | """ |
| | 520 | Checks if the argument list is in the cache. |
| | 521 | |
| | 522 | EXAMPLES:: |
| | 523 | |
| | 524 | sage: class Foo: |
| | 525 | ... def __init__(self, x): |
| | 526 | ... self._x = x |
| | 527 | ... @cached_method |
| | 528 | ... def f(self, z, y=0): |
| | 529 | ... return self._x*z+y |
| | 530 | ... |
| | 531 | sage: a = Foo(2) |
| | 532 | sage: a.f.is_in_cache(3) |
| | 533 | False |
| | 534 | sage: a.f(3) |
| | 535 | 6 |
| | 536 | sage: a.f.is_in_cache(3,y=0) |
| | 537 | True |
| | 538 | """ |
| | 539 | return self._fix_to_pos(*args, **kwds) in (<dict>self.cache) |
| | 540 | |
| | 541 | def set_cache(self, value, *args, **kwds): |
| | 542 | """ |
| | 543 | Set the value for those args and keyword args |
| | 544 | Mind the unintuitive syntax (value first). |
| | 545 | Any idea on how to improve that welcome! |
| | 546 | |
| | 547 | EXAMPLES:: |
| | 548 | |
| | 549 | sage: g = CachedFunction(number_of_partitions) |
| | 550 | sage: a = g(5) |
| | 551 | sage: g.get_cache() |
| | 552 | {((5, None, 'default'), ()): 7} |
| | 553 | sage: g.set_cache(17, 5) |
| | 554 | sage: g.get_cache() |
| | 555 | {((5, None, 'default'), ()): 17} |
| | 556 | sage: g(5) |
| | 557 | 17 |
| | 558 | |
| | 559 | DEVELOPER NOTE: |
| | 560 | |
| | 561 | Is there a way to use the following intuitive syntax? |
| | 562 | |
| | 563 | :: |
| | 564 | |
| | 565 | sage: g(5) = 19 # todo: not implemented |
| | 566 | sage: g(5) # todo: not implemented |
| | 567 | 19 |
| | 568 | """ |
| | 569 | (<dict>self.cache)[self._fix_to_pos(*args, **kwds)] = value |
| | 570 | |
| | 571 | def get_key(self, *args, **kwds): |
| | 572 | """ |
| | 573 | Returns the key in the cache to be used when args |
| | 574 | and kwds are passed in as parameters. |
| | 575 | |
| | 576 | EXAMPLES:: |
| | 577 | |
| | 578 | sage: @cached_function |
| | 579 | ... def foo(x): |
| | 580 | ... return x^2 |
| | 581 | ... |
| | 582 | sage: foo(2) |
| | 583 | 4 |
| | 584 | sage: foo.get_key(2) |
| | 585 | ((2,), ()) |
| | 586 | sage: foo.get_key(x=3) |
| | 587 | ((3,), ()) |
| | 588 | """ |
| | 589 | return self._fix_to_pos(*args, **kwds) |
| | 590 | |
| | 591 | def __repr__(self): |
| | 592 | """ |
| | 593 | EXAMPLES:: |
| | 594 | |
| | 595 | sage: g = CachedFunction(number_of_partitions) |
| | 596 | sage: g # indirect doctest |
| | 597 | Cached version of <function number_of_partitions at 0x...> |
| | 598 | """ |
| | 599 | try: |
| | 600 | return "Cached version of %s"%self.f |
| | 601 | except AttributeError: |
| | 602 | return "Cached version of a method (pending reassignment)" |
| | 603 | |
| | 604 | cpdef clear_cache(self): |
| | 605 | """ |
| | 606 | Clear the cache dictionary. |
| | 607 | |
| | 608 | EXAMPLES:: |
| | 609 | |
| | 610 | sage: g = CachedFunction(number_of_partitions) |
| | 611 | sage: a = g(5) |
| | 612 | sage: g.get_cache() |
| | 613 | {((5, None, 'default'), ()): 7} |
| | 614 | sage: g.clear_cache() |
| | 615 | sage: g.get_cache() |
| | 616 | {} |
| | 617 | """ |
| | 618 | cdef object cache = self.cache |
| | 619 | for key in cache.keys(): |
| | 620 | del cache[key] |
| | 621 | |
| | 622 | def precompute(self, arglist, num_processes=1): |
| | 623 | """ |
| | 624 | Cache values for a number of inputs. Do the computation |
| | 625 | in parallel, and only bother to compute values that we |
| | 626 | haven't already cached. |
| | 627 | |
| | 628 | EXAMPLES:: |
| | 629 | |
| | 630 | sage: @cached_function |
| | 631 | ... def oddprime_factors(n): |
| | 632 | ... l = [p for p,e in factor(n) if p != 2] |
| | 633 | ... return len(l) |
| | 634 | sage: oddprime_factors.precompute(range(1,100), 4) |
| | 635 | sage: oddprime_factors.cache[(25,),()] |
| | 636 | 1 |
| | 637 | """ |
| | 638 | from sage.parallel.decorate import parallel, normalize_input |
| | 639 | P = parallel(num_processes)(self.f) |
| | 640 | has_key = self.cache.has_key |
| | 641 | get_key = self._fix_to_pos |
| | 642 | new = lambda x: not has_key(get_key(*x[0],**x[1])) |
| | 643 | arglist = filter(new, map(normalize_input, arglist)) |
| | 644 | for ((args,kwargs), val) in P(arglist): |
| | 645 | self.set_cache(val, *args, **kwargs) |
| | 646 | |
| | 647 | |
| | 648 | cached_function = CachedFunction |
| | 649 | |
| | 650 | class CachedMethodPickle(object): |
| | 651 | """ |
| | 652 | This class helps to unpickle cached methods. |
| | 653 | |
| | 654 | NOTE: |
| | 655 | |
| | 656 | Since trac ticket #8611, a cached method is an attribute |
| | 657 | of the instance (provided that it has a ``__dict__``). |
| | 658 | Hence, when pickling the instance, it would be attempted |
| | 659 | to pickle that attribute as well, but this is a problem, |
| | 660 | since functions can not be pickled, currently. Therefore, |
| | 661 | we replace the actual cached method by a place holder, |
| | 662 | that kills itself as soon as any attribute is requested. |
| | 663 | Then, the original cached attribute is reinstated. But the |
| | 664 | cached values are in fact saved. |
| | 665 | |
| | 666 | EXAMPLE:: |
| | 667 | |
| | 668 | sage: R.<x, y, z> = PolynomialRing(QQ, 3) |
| | 669 | sage: I = R*(x^3 + y^3 + z^3,x^4-y^4) |
| | 670 | sage: I.groebner_basis() |
| | 671 | [y^5*z^3 - 1/4*x^2*z^6 + 1/2*x*y*z^6 + 1/4*y^2*z^6, x^2*y*z^3 - x*y^2*z^3 + 2*y^3*z^3 + z^6, x*y^3 + y^4 + x*z^3, x^3 + y^3 + z^3] |
| | 672 | sage: I.groebner_basis |
| | 673 | Cached version of <function groebner_basis at 0x...> |
| | 674 | |
| | 675 | We now pickle and unpickle the ideal. The cached method |
| | 676 | ``groebner_basis`` is replaced by a placeholder:: |
| | 677 | |
| | 678 | sage: J = loads(dumps(I)) |
| | 679 | sage: J.groebner_basis |
| | 680 | Pickle of the cached method "groebner_basis" |
| | 681 | |
| | 682 | But as soon as any other attribute is requested from the |
| | 683 | placeholder, it replaces itself by the cached method, and |
| | 684 | the entries of the cache are actually preserved:: |
| | 685 | |
| | 686 | sage: J.groebner_basis.is_in_cache() |
| | 687 | True |
| | 688 | sage: J.groebner_basis |
| | 689 | Cached version of <function groebner_basis at 0x...> |
| | 690 | sage: J.groebner_basis() == I.groebner_basis() |
| | 691 | True |
| | 692 | |
| | 693 | TESTS: |
| | 694 | |
| | 695 | Since Trac Ticket #11115, there is a special implementation for |
| | 696 | cached methods that don't take arguments:: |
| | 697 | |
| | 698 | sage: P.<a,b,c,d> = QQ[] |
| | 699 | sage: I = P*[a,b] |
| | 700 | sage: type(I.gens) |
| | 701 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 702 | sage: type(I.groebner_basis) |
| | 703 | <type 'sage.misc.cachefunc.CachedMethodCaller'> |
| | 704 | |
| | 705 | We demonstrate that both implementations can be pickled and |
| | 706 | preserve the cache. For that purpose, we assign nonsense to the |
| | 707 | cache. Of course, it is a very bad idea to override the cache in |
| | 708 | that way. So, please don't try this at home:: |
| | 709 | |
| | 710 | sage: I.groebner_basis.set_cache('foo',algorithm='singular') |
| | 711 | sage: I.groebner_basis(algorithm='singular') |
| | 712 | 'foo' |
| | 713 | sage: I.gens.set_cache('bar') |
| | 714 | sage: I.gens() |
| | 715 | 'bar' |
| | 716 | sage: J = loads(dumps(I)) |
| | 717 | sage: J.gens() |
| | 718 | 'bar' |
| | 719 | sage: J.groebner_basis(algorithm='singular') |
| | 720 | 'foo' |
| | 721 | |
| | 722 | Anyway, the cache will be automatically reconstructed after |
| | 723 | clearing it:: |
| | 724 | |
| | 725 | sage: J.gens.clear_cache() |
| | 726 | sage: J.gens() |
| | 727 | [a, b] |
| | 728 | sage: J.groebner_basis.clear_cache() |
| | 729 | sage: J.groebner_basis(algorithm='singular') |
| | 730 | [a, b] |
| | 731 | |
| | 732 | AUTHOR: |
| | 733 | |
| | 734 | - Simon King (2011-01) |
| | 735 | """ |
| | 736 | def __init__(self, inst, name, cache=None): |
| | 737 | """ |
| | 738 | INPUT: |
| | 739 | |
| | 740 | - ``inst`` - some instance. |
| | 741 | - ``name`` (string) - usually the name of an attribute |
| | 742 | of ``inst`` to which ``self`` is assigned. |
| | 743 | |
| | 744 | TEST:: |
| | 745 | |
| | 746 | sage: from sage.misc.cachefunc import CachedMethodPickle |
| | 747 | sage: P = CachedMethodPickle(1, 'foo') |
| | 748 | sage: P |
| | 749 | Pickle of the cached method "foo" |
| | 750 | |
| | 751 | """ |
| | 752 | self._instance = inst |
| | 753 | self._name = name |
| | 754 | self._cache = cache |
| | 755 | def __repr__(self): |
| | 756 | """ |
| | 757 | TEST:: |
| | 758 | |
| | 759 | sage: R.<x, y, z> = PolynomialRing(QQ, 3) |
| | 760 | sage: I = R*(x^3 + y^3 + z^3,x^4-y^4) |
| | 761 | sage: G = I.groebner_basis() |
| | 762 | sage: J = loads(dumps(I)) |
| | 763 | sage: J.groebner_basis #indirect doctest |
| | 764 | Pickle of the cached method "groebner_basis" |
| | 765 | """ |
| | 766 | return 'Pickle of the cached method "%s"'%self._name |
| | 767 | def __reduce__(self): |
| | 768 | """ |
| | 769 | This class is a pickle. However, sometimes, pickles |
| | 770 | need to be pickled another time. |
| | 771 | |
| | 772 | TEST:: |
| | 773 | |
| | 774 | sage: PF = WeylGroup(['A',3]).pieri_factors() |
| | 775 | sage: a = PF.an_element() |
| | 776 | sage: a.bruhat_lower_covers() |
| | 777 | [[0 1 0 0] |
| | 778 | [0 0 1 0] |
| | 779 | [1 0 0 0] |
| | 780 | [0 0 0 1], [0 1 0 0] |
| | 781 | [1 0 0 0] |
| | 782 | [0 0 0 1] |
| | 783 | [0 0 1 0], [1 0 0 0] |
| | 784 | [0 0 1 0] |
| | 785 | [0 0 0 1] |
| | 786 | [0 1 0 0]] |
| | 787 | sage: b = loads(dumps(a)) |
| | 788 | sage: b.bruhat_lower_covers |
| | 789 | Pickle of the cached method "bruhat_lower_covers" |
| | 790 | |
| | 791 | When we now pickle ``b``, the pickle of the cached method |
| | 792 | needs to be taken care of:: |
| | 793 | |
| | 794 | sage: c = loads(dumps(b)) # indirect doctest |
| | 795 | sage: c.bruhat_lower_covers |
| | 796 | Pickle of the cached method "bruhat_lower_covers" |
| | 797 | sage: c.bruhat_lower_covers() |
| | 798 | [[0 1 0 0] |
| | 799 | [0 0 1 0] |
| | 800 | [1 0 0 0] |
| | 801 | [0 0 0 1], [0 1 0 0] |
| | 802 | [1 0 0 0] |
| | 803 | [0 0 0 1] |
| | 804 | [0 0 1 0], [1 0 0 0] |
| | 805 | [0 0 1 0] |
| | 806 | [0 0 0 1] |
| | 807 | [0 1 0 0]] |
| | 808 | |
| | 809 | """ |
| | 810 | return CachedMethodPickle,(self._instance,self._name,self._cache) |
| | 811 | def __call__(self,*args,**kwds): |
| | 812 | """ |
| | 813 | The purpose of this call method is to kill ``self`` and to |
| | 814 | replace it by an actual :class:`CachedMethodCaller`. The last |
| | 815 | thing that ``self`` does before disappearing is to call the |
| | 816 | :class:`CachedMethodCaller` and return the result. |
| | 817 | |
| | 818 | EXAMPLE:: |
| | 819 | |
| | 820 | sage: P.<a,b,c,d> = QQ[] |
| | 821 | sage: I = P*[a,b] |
| | 822 | sage: I.gens |
| | 823 | Cached version of <function gens at 0x...> |
| | 824 | sage: J = loads(dumps(I)) |
| | 825 | sage: J.gens |
| | 826 | Pickle of the cached method "gens" |
| | 827 | sage: J.gens() # indirect doctest |
| | 828 | [a, b] |
| | 829 | sage: J.gens |
| | 830 | Cached version of <function gens at 0x...> |
| | 831 | |
| | 832 | """ |
| | 833 | self._instance.__dict__.__delitem__(self._name) |
| | 834 | CM = getattr(self._instance,self._name) |
| | 835 | if self._cache is not None: |
| | 836 | if isinstance(CM, CachedMethodCallerNoArgs): |
| | 837 | CM.cache = self._cache |
| | 838 | else: |
| | 839 | for k,v in self._cache: |
| | 840 | CM.cache[k] = v |
| | 841 | return CM(*args,**kwds) |
| | 842 | |
| | 843 | def __getattr__(self,s): |
| | 844 | """ |
| | 845 | TEST:: |
| | 846 | |
| | 847 | sage: R.<x, y, z> = PolynomialRing(QQ, 3) |
| | 848 | sage: I = R*(x^3 + y^3 + z^3,x^4-y^4) |
| | 849 | sage: G = I.groebner_basis() |
| | 850 | sage: J = loads(dumps(I)) |
| | 851 | sage: J.groebner_basis |
| | 852 | Pickle of the cached method "groebner_basis" |
| | 853 | |
| | 854 | If an attribute of name ``s`` is requested (say, |
| | 855 | ``is_in_cache``), the attribute ``self._name`` of |
| | 856 | ``self._instance`` is deleted. Then, the attribute |
| | 857 | of name ``s`` of the attribute ``self._name`` of |
| | 858 | ``self._instance`` is requested. Since ``self._name`` |
| | 859 | is a cached method defined for the class of |
| | 860 | ``self._instance``, retrieving the just-deleted |
| | 861 | attribute ``self._name`` succeeds. |
| | 862 | |
| | 863 | In that way, the unpickling of the cached method is |
| | 864 | finally accomplished:: |
| | 865 | |
| | 866 | sage: J.groebner_basis.is_in_cache() #indirect doctest |
| | 867 | True |
| | 868 | sage: J.groebner_basis |
| | 869 | Cached version of <function groebner_basis at 0x...> |
| | 870 | |
| | 871 | """ |
| | 872 | self._instance.__dict__.__delitem__(self._name) |
| | 873 | CM = getattr(self._instance,self._name) |
| | 874 | if self._cache is not None: |
| | 875 | if isinstance(CM, CachedMethodCallerNoArgs): |
| | 876 | CM.cache = self._cache |
| | 877 | else: |
| | 878 | for k,v in self._cache: |
| | 879 | CM.cache[k] = v |
| | 880 | return getattr(CM,s) |
| | 881 | |
| | 882 | cdef class CachedMethodCaller(CachedFunction): |
| | 883 | """ |
| | 884 | Utility class that is used by :class:`CachedMethod` to bind a |
| | 885 | cached method to an instance. |
| | 886 | |
| | 887 | NOTE: |
| | 888 | |
| | 889 | Since Trac Ticket #11115, there is a special implementation |
| | 890 | :class:`CachedMethodCallerNoArgs` for methods that do not take |
| | 891 | arguments. |
| | 892 | |
| | 893 | EXAMPLE:: |
| | 894 | |
| | 895 | sage: class A: |
| | 896 | ... @cached_method |
| | 897 | ... def bar(self,x): |
| | 898 | ... return x^2 |
| | 899 | sage: a = A() |
| | 900 | sage: a.bar |
| | 901 | Cached version of <function bar at 0x...> |
| | 902 | sage: type(a.bar) |
| | 903 | <type 'sage.misc.cachefunc.CachedMethodCaller'> |
| | 904 | sage: a.bar(2) is a.bar(x=2) |
| | 905 | True |
| | 906 | |
| | 907 | """ |
| | 908 | def __init__(self, CachedMethod cachedmethod, inst, cache=None, inst_in_key=False, name=None): |
| | 909 | """ |
| | 910 | EXAMPLES:: |
| | 911 | |
| | 912 | sage: class Foo: |
| | 913 | ... def __init__(self, x): |
| | 914 | ... self._x = x |
| | 915 | ... @cached_method |
| | 916 | ... def f(self,*args): |
| | 917 | ... return self._x^2 |
| | 918 | ... |
| | 919 | sage: a = Foo(2) |
| | 920 | sage: a.f.get_cache() |
| | 921 | {} |
| | 922 | sage: a.f() |
| | 923 | 4 |
| | 924 | sage: a.f.get_cache() |
| | 925 | {((), ()): 4} |
| | 926 | """ |
| | 927 | # initialize CachedFunction, but re-use the ArgumentFixer |
| | 928 | self._common_init(cachedmethod._cachedfunc.f, cachedmethod._cachedfunc._argument_fixer, name=name) |
| | 929 | self.cache = {} if cache is None else cache |
| | 930 | self._instance = inst |
| | 931 | self._inst_in_key = inst_in_key |
| | 932 | self._cachedmethod = cachedmethod |
| | 933 | |
| | 934 | def __reduce__(self): |
| | 935 | """ |
| | 936 | The pickle of a :class:`CachedMethodCaller` unpickles |
| | 937 | to a :class:`CachedMethodPickle`, that is able to replace |
| | 938 | itself by a copy of the original :class:`CachedMethodCaller`. |
| | 939 | |
| | 940 | TEST:: |
| | 941 | |
| | 942 | sage: R.<x, y, z> = PolynomialRing(QQ, 3) |
| | 943 | sage: I = R*(x^3 + y^3 + z^3,x^4-y^4) |
| | 944 | sage: G = I.groebner_basis() |
| | 945 | sage: J = loads(dumps(I)) #indirect doctest |
| | 946 | sage: J.groebner_basis |
| | 947 | Pickle of the cached method "groebner_basis" |
| | 948 | sage: J.groebner_basis.is_in_cache() |
| | 949 | True |
| | 950 | sage: J.groebner_basis |
| | 951 | Cached version of <function groebner_basis at 0x...> |
| | 952 | |
| | 953 | """ |
| | 954 | # if hasattr(self._instance,self._cachedmethod._cache_name): |
| | 955 | # return CachedMethodPickle,(self._instance,self.__name__) |
| | 956 | if isinstance(self._cachedmethod, CachedInParentMethod) or hasattr(self._instance,self._cachedmethod._cache_name): |
| | 957 | return CachedMethodPickle,(self._instance,self.__name__) |
| | 958 | return CachedMethodPickle,(self._instance,self.__name__,self.cache.items()) |
| | 959 | |
| | 960 | def _instance_call(self, *args, **kwds): |
| | 961 | """ |
| | 962 | Call the cached method without using the cache. |
| | 963 | |
| | 964 | EXAMPLE:: |
| | 965 | |
| | 966 | sage: P.<a,b,c,d> = QQ[] |
| | 967 | sage: I = P*[a,b] |
| | 968 | sage: I.groebner_basis() |
| | 969 | [a, b] |
| | 970 | sage: I.groebner_basis._instance_call() is I.groebner_basis() |
| | 971 | False |
| | 972 | sage: I.groebner_basis._instance_call() == I.groebner_basis() |
| | 973 | True |
| | 974 | |
| | 975 | """ |
| | 976 | return self._cachedmethod._instance_call(self._instance, *args, **kwds) |
| | 977 | |
| | 978 | def __call__(self, *args, **kwds): |
| | 979 | """ |
| | 980 | Call the cached method. |
| | 981 | |
| | 982 | TESTS:: |
| | 983 | |
| | 984 | sage: class Foo: |
| | 985 | ... @cached_method |
| | 986 | ... def f(self, x,y=1): |
| | 987 | ... return x+y |
| | 988 | ... |
| | 989 | sage: a = Foo() |
| | 990 | sage: a.f(1) #indirect doctest |
| | 991 | 2 |
| | 992 | |
| | 993 | The result is cached, taking into account |
| | 994 | the three ways of providing (named) arguments:: |
| | 995 | |
| | 996 | sage: a.f(5) is a.f(5,1) |
| | 997 | True |
| | 998 | sage: a.f(5) is a.f(5,y=1) |
| | 999 | True |
| | 1000 | sage: a.f(5) is a.f(y=1,x=5) |
| | 1001 | True |
| | 1002 | |
| | 1003 | We test that #5843 is fixed:: |
| | 1004 | |
| | 1005 | sage: class Foo: |
| | 1006 | ... def __init__(self, x): |
| | 1007 | ... self._x = x |
| | 1008 | ... @cached_method |
| | 1009 | ... def f(self, y): |
| | 1010 | ... return self._x |
| | 1011 | ... |
| | 1012 | sage: a = Foo(2) |
| | 1013 | sage: b = Foo(3) |
| | 1014 | sage: a.f(b.f) |
| | 1015 | 2 |
| | 1016 | """ |
| | 1017 | # We shortcut a common case of no arguments |
| | 1018 | # and we avoid calling another python function, |
| | 1019 | # although that means to duplicate code. |
| | 1020 | cdef int lenargs |
| | 1021 | cdef int nargs |
| | 1022 | cdef tuple k |
| | 1023 | cdef dict cache = self.cache |
| | 1024 | if kwds: |
| | 1025 | if self._inst_in_key: |
| | 1026 | k = (self._instance,self._fix_to_pos(*args, **kwds)) |
| | 1027 | else: |
| | 1028 | k = self._fix_to_pos(*args, **kwds) |
| | 1029 | else: |
| | 1030 | if args: |
| | 1031 | lenargs = len(args) |
| | 1032 | nargs = self._argument_fixer._nargs |
| | 1033 | if self._inst_in_key: |
| | 1034 | if lenargs>=nargs: |
| | 1035 | k = (self._instance,(args,())) |
| | 1036 | else: |
| | 1037 | k = (self._instance,(<tuple>args+(<tuple>self._argument_fixer._default_tuple)[-nargs+lenargs:],())) |
| | 1038 | else: |
| | 1039 | if lenargs>=nargs: |
| | 1040 | k = (args,()) |
| | 1041 | else: |
| | 1042 | k = (<tuple>args+(<tuple>self._argument_fixer._default_tuple)[-nargs+lenargs:],()) |
| | 1043 | elif self._default_key is not None: |
| | 1044 | k = self._default_key |
| | 1045 | else: |
| | 1046 | if self._inst_in_key: |
| | 1047 | k = self._default_key = (self._instance,self._fix_to_pos()) |
| | 1048 | else: |
| | 1049 | k = self._default_key = self._fix_to_pos() |
| | 1050 | try: |
| | 1051 | return cache[k] |
| | 1052 | except KeyError: |
| | 1053 | w = self._cachedmethod._instance_call(self._instance, *args, **kwds) |
| | 1054 | cache[k] = w |
| | 1055 | return w |
| | 1056 | |
| | 1057 | def get_key(self, *args, **kwds): |
| | 1058 | """ |
| | 1059 | Convert arguments to the key for this instance's cache. |
| | 1060 | |
| | 1061 | EXAMPLES:: |
| | 1062 | |
| | 1063 | sage: class Foo: |
| | 1064 | ... def __init__(self, x): |
| | 1065 | ... self._x = x |
| | 1066 | ... @cached_method |
| | 1067 | ... def f(self, y, z=0): |
| | 1068 | ... return self._x * y + z |
| | 1069 | ... |
| | 1070 | sage: a = Foo(2) |
| | 1071 | sage: z = a.f(37) |
| | 1072 | sage: k = a.f.get_key(37); k |
| | 1073 | ((37, 0), ()) |
| | 1074 | sage: a.f.get_cache()[k] is z |
| | 1075 | True |
| | 1076 | |
| | 1077 | Note that the method does not test whether there are |
| | 1078 | too many arguments, or wrong argument names:: |
| | 1079 | |
| | 1080 | sage: a.f.get_key(1,2,3,x=4,y=5,z=6) |
| | 1081 | ((1, 2, 3), (('x', 4), ('y', 5), ('z', 6))) |
| | 1082 | |
| | 1083 | It does, however, take into account the different |
| | 1084 | ways of providing named arguments, possibly with a |
| | 1085 | default value:: |
| | 1086 | |
| | 1087 | sage: a.f.get_key(5) |
| | 1088 | ((5, 0), ()) |
| | 1089 | sage: a.f.get_key(y=5) |
| | 1090 | ((5, 0), ()) |
| | 1091 | sage: a.f.get_key(5,0) |
| | 1092 | ((5, 0), ()) |
| | 1093 | sage: a.f.get_key(5,z=0) |
| | 1094 | ((5, 0), ()) |
| | 1095 | sage: a.f.get_key(y=5,z=0) |
| | 1096 | ((5, 0), ()) |
| | 1097 | |
| | 1098 | """ |
| | 1099 | if self._inst_in_key: |
| | 1100 | return (self._instance,self._fix_to_pos(*args,**kwds)) |
| | 1101 | return self._fix_to_pos(*args,**kwds) |
| | 1102 | |
| | 1103 | def __get__(self, inst, cls): #cls=None): |
| | 1104 | r""" |
| | 1105 | Get a :class:`CachedMethodCaller` bound to a specific |
| | 1106 | instance of the class of the cached method. |
| | 1107 | |
| | 1108 | NOTE: |
| | 1109 | |
| | 1110 | :class:`CachedMethodCaller` has a separate ``__get__`` |
| | 1111 | since the categories framework creates and caches the |
| | 1112 | return value of ``CachedMethod.__get__`` with |
| | 1113 | ``inst==None``. |
| | 1114 | |
| | 1115 | This getter attempts to assign a bound method as an |
| | 1116 | attribute to the given instance. If this is not |
| | 1117 | possible (for example, for some extension classes), |
| | 1118 | it is attempted to find an attribute ``__cached_methods``, |
| | 1119 | and store/retrieve the bound method there. In that |
| | 1120 | way, cached methods can be implemented for extension |
| | 1121 | classes deriving from :class:`~sage.structure.parent.Parent` |
| | 1122 | and :class:`~sage.structure.element.Element`. |
| | 1123 | |
| | 1124 | TESTS: |
| | 1125 | |
| | 1126 | Due to the separate ``__get__`` method, it is possible |
| | 1127 | to define a cached method in one class and use it as |
| | 1128 | an attribute of another class. |
| | 1129 | |
| | 1130 | sage: class Foo: |
| | 1131 | ... @cached_method |
| | 1132 | ... def f(self, y): |
| | 1133 | ... return y - 1 |
| | 1134 | sage: class Bar: |
| | 1135 | ... f = Foo.f |
| | 1136 | sage: b1 = Bar() |
| | 1137 | sage: b2 = Bar() |
| | 1138 | |
| | 1139 | The :class:`CachedMethod` is replaced by an instance |
| | 1140 | of :class:`CachedMethodCaller` that (by trac ticket |
| | 1141 | #8611) is set as an attribute. Hence, we have:: |
| | 1142 | |
| | 1143 | sage: b1.f is b1.f |
| | 1144 | True |
| | 1145 | |
| | 1146 | Any instance of ``Bar`` gets its own instance of |
| | 1147 | :class:`CachedMethodCaller``:: |
| | 1148 | |
| | 1149 | sage: b1.f is b2.f |
| | 1150 | False |
| | 1151 | |
| | 1152 | The method caller knows the instance that it belongs |
| | 1153 | to:: |
| | 1154 | |
| | 1155 | sage: Foo.f._instance is None |
| | 1156 | True |
| | 1157 | sage: b1.f._instance is b1 |
| | 1158 | True |
| | 1159 | sage: b2.f._instance is b2 |
| | 1160 | True |
| | 1161 | |
| | 1162 | An extension class can inherit a cached method from the |
| | 1163 | parent or element class of a category (trac ticket #11115). |
| | 1164 | See :class:`CachedMethodCaller` for examples. |
| | 1165 | |
| | 1166 | """ |
| | 1167 | # This is for Parents or Elements that do not allow attribute assignment |
| | 1168 | try: |
| | 1169 | return (<dict>inst.__cached_methods).__getitem__(self._cachedmethod._cachedfunc.__name__) |
| | 1170 | except (AttributeError,TypeError,KeyError): |
| | 1171 | pass |
| | 1172 | Caller = CachedMethodCaller(self._cachedmethod, inst, cache=self._cachedmethod._get_instance_cache(inst), inst_in_key=self._inst_in_key, name=self._cachedmethod._cachedfunc.__name__) |
| | 1173 | try: |
| | 1174 | setattr(inst,self._cachedmethod._cachedfunc.__name__, Caller) |
| | 1175 | return Caller |
| | 1176 | except AttributeError,msg: |
| | 1177 | pass |
| | 1178 | try: |
| | 1179 | if inst.__cached_methods is None: |
| | 1180 | inst.__cached_methods = {self._cachedmethod._cachedfunc.__name__ : Caller} |
| | 1181 | else: |
| | 1182 | (<dict>inst.__cached_methods).__setitem__(self._cachedmethod._cachedfunc.__name__, Caller) |
| | 1183 | except AttributeError,msg: |
| | 1184 | pass |
| | 1185 | return Caller |
| | 1186 | |
| | 1187 | cdef class CachedMethodCallerNoArgs(CachedFunction): |
| | 1188 | """ |
| | 1189 | Utility class that is used by :class:`CachedMethod` to bind a |
| | 1190 | cached method to an instance, in the case of a method that does |
| | 1191 | not accept any arguments except ``self``. |
| | 1192 | |
| | 1193 | NOTE: |
| | 1194 | |
| | 1195 | The return value ``None`` would not be cached. So, if you have |
| | 1196 | a method that does not accept arguments and may return ``None`` |
| | 1197 | after a lengthy computation, then ``@cached_method`` should not |
| | 1198 | be used. |
| | 1199 | |
| | 1200 | EXAMPLE:: |
| | 1201 | |
| | 1202 | sage: P.<a,b,c,d> = QQ[] |
| | 1203 | sage: I = P*[a,b] |
| | 1204 | sage: I.gens |
| | 1205 | Cached version of <function gens at 0x...> |
| | 1206 | sage: type(I.gens) |
| | 1207 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 1208 | sage: I.gens is I.gens |
| | 1209 | True |
| | 1210 | sage: I.gens() is I.gens() |
| | 1211 | True |
| | 1212 | |
| | 1213 | AUTHOR: |
| | 1214 | |
| | 1215 | - Simon King (2011-04) |
| | 1216 | """ |
| | 1217 | def __init__(self, inst, f, cache=None, name=None): |
| | 1218 | """ |
| | 1219 | EXAMPLES:: |
| | 1220 | |
| | 1221 | sage: class Foo: |
| | 1222 | ... def __init__(self, x): |
| | 1223 | ... self._x = x |
| | 1224 | ... @cached_method |
| | 1225 | ... def f(self): |
| | 1226 | ... return self._x^2 |
| | 1227 | ... |
| | 1228 | sage: a = Foo(2) |
| | 1229 | sage: print a.f.get_cache() |
| | 1230 | None |
| | 1231 | sage: a.f() |
| | 1232 | 4 |
| | 1233 | sage: a.f.get_cache() |
| | 1234 | 4 |
| | 1235 | |
| | 1236 | """ |
| | 1237 | # initialize CachedFunction |
| | 1238 | if isinstance(f,basestring): |
| | 1239 | try: |
| | 1240 | F = getattr(inst.__class__,f) |
| | 1241 | except AttributeError: |
| | 1242 | F = getattr(inst,f) |
| | 1243 | if isinstance(F,CachedFunction): |
| | 1244 | f = F.f |
| | 1245 | else: |
| | 1246 | f = F |
| | 1247 | self._common_init(f, None, name=name) |
| | 1248 | # This is for unpickling a CachedMethodCallerNoArgs out |
| | 1249 | # of an old CachedMethodCaller: |
| | 1250 | cachename = '_cache__' + self.__name__ |
| | 1251 | if hasattr(inst, cachename): |
| | 1252 | # This is for data that are pickled in an old format |
| | 1253 | CACHE = getattr(inst, cachename) |
| | 1254 | if len(CACHE)>1: |
| | 1255 | raise TypeError, "Apparently you are opening a pickle in which '%s' was a method accepting arguments"%name |
| | 1256 | if len(CACHE)==1: |
| | 1257 | self.cache = CACHE.values()[0] |
| | 1258 | else: |
| | 1259 | self.cache = cache |
| | 1260 | delattr(inst, cachename) |
| | 1261 | else: |
| | 1262 | self.cache = cache # None means: the underlying method will be called |
| | 1263 | self._instance = inst |
| | 1264 | |
| | 1265 | def __reduce__(self): |
| | 1266 | """ |
| | 1267 | Since functions can not be pickled, the cached method caller |
| | 1268 | is pickled by a :class:`CachedMethodPickle`, that replaces |
| | 1269 | itself by an actual :class:`CachedMethodCallerNoArgs` as soon |
| | 1270 | as it is asked to do anything. |
| | 1271 | |
| | 1272 | TEST:: |
| | 1273 | |
| | 1274 | sage: P.<a,b,c,d> = QQ[] |
| | 1275 | sage: I = P*[a,b] |
| | 1276 | sage: I.gens() |
| | 1277 | [a, b] |
| | 1278 | sage: I.gens |
| | 1279 | Cached version of <function gens at 0x...> |
| | 1280 | sage: J = loads(dumps(I)) |
| | 1281 | sage: J.gens |
| | 1282 | Pickle of the cached method "gens" |
| | 1283 | sage: J.gens.cache |
| | 1284 | [a, b] |
| | 1285 | sage: J.gens |
| | 1286 | Cached version of <function gens at 0x...> |
| | 1287 | |
| | 1288 | """ |
| | 1289 | return CachedMethodPickle,(self._instance,self.__name__,self.cache) |
| | 1290 | |
| | 1291 | def _instance_call(self): |
| | 1292 | """ |
| | 1293 | Call the cached method without using the cache. |
| | 1294 | |
| | 1295 | EXAMPLE:: |
| | 1296 | |
| | 1297 | sage: P.<a,b,c,d> = QQ[] |
| | 1298 | sage: I = P*[a,b] |
| | 1299 | sage: I.gens() |
| | 1300 | [a, b] |
| | 1301 | sage: I.gens._instance_call() is I.gens() |
| | 1302 | False |
| | 1303 | sage: I.gens._instance_call() == I.gens() |
| | 1304 | True |
| | 1305 | |
| | 1306 | """ |
| | 1307 | return self.f(self._instance) |
| | 1308 | |
| | 1309 | def __call__(self): |
| | 1310 | """ |
| | 1311 | Call the cached method. |
| | 1312 | |
| | 1313 | EXAMPLE:: |
| | 1314 | |
| | 1315 | sage: P.<a,b,c,d> = QQ[] |
| | 1316 | sage: I = P*[a,b] |
| | 1317 | sage: I.gens() # indirect doctest |
| | 1318 | [a, b] |
| | 1319 | sage: I.gens() is I.gens() |
| | 1320 | True |
| | 1321 | |
| | 1322 | """ |
| | 1323 | if self.cache is None: |
| | 1324 | f = self.f |
| | 1325 | self.cache = f(self._instance) |
| | 1326 | return self.cache |
| | 1327 | |
| | 1328 | def set_cache(self, value): |
| | 1329 | """ |
| | 1330 | Override the cache with a specific value. |
| | 1331 | |
| | 1332 | NOTE: |
| | 1333 | |
| | 1334 | ``None`` is not suitable for a cached value. It would be |
| | 1335 | interpreted as an empty cache, forcing a new computation. |
| | 1336 | |
| | 1337 | EXAMPLES:: |
| | 1338 | |
| | 1339 | sage: P.<a,b,c,d> = QQ[] |
| | 1340 | sage: I = P*[a,b] |
| | 1341 | sage: I.gens() |
| | 1342 | [a, b] |
| | 1343 | sage: I.gens.set_cache('bar') |
| | 1344 | sage: I.gens() |
| | 1345 | 'bar' |
| | 1346 | |
| | 1347 | The cache can be emptied and thus the original value will |
| | 1348 | be reconstructed:: |
| | 1349 | |
| | 1350 | sage: I.gens.clear_cache() |
| | 1351 | sage: I.gens() |
| | 1352 | [a, b] |
| | 1353 | |
| | 1354 | The attempt to assign ``None`` to the cache fails:: |
| | 1355 | |
| | 1356 | sage: I.gens.set_cache(None) |
| | 1357 | sage: I.gens() |
| | 1358 | [a, b] |
| | 1359 | |
| | 1360 | """ |
| | 1361 | self.cache = value |
| | 1362 | |
| | 1363 | cpdef clear_cache(self): |
| | 1364 | r""" |
| | 1365 | Clear the cache dictionary. |
| | 1366 | |
| | 1367 | EXAMPLES:: |
| | 1368 | |
| | 1369 | sage: P.<a,b,c,d> = QQ[] |
| | 1370 | sage: I = P*[a,b] |
| | 1371 | sage: I.gens() |
| | 1372 | [a, b] |
| | 1373 | sage: I.gens.set_cache('bar') |
| | 1374 | sage: I.gens() |
| | 1375 | 'bar' |
| | 1376 | |
| | 1377 | The cache can be emptied and thus the original value will |
| | 1378 | be reconstructed:: |
| | 1379 | |
| | 1380 | sage: I.gens.clear_cache() |
| | 1381 | sage: I.gens() |
| | 1382 | [a, b] |
| | 1383 | |
| | 1384 | """ |
| | 1385 | self.cache = None |
| | 1386 | |
| | 1387 | def is_in_cache(self): |
| | 1388 | """ |
| | 1389 | Answers whether the return value is already in the cache. |
| | 1390 | |
| | 1391 | NOTE: |
| | 1392 | |
| | 1393 | Recall that a cached method without arguments can not cache |
| | 1394 | the return value ``None``. |
| | 1395 | |
| | 1396 | EXAMPLE:: |
| | 1397 | |
| | 1398 | sage: P.<x,y> = QQ[] |
| | 1399 | sage: I = P*[x,y] |
| | 1400 | sage: I.gens.is_in_cache() |
| | 1401 | False |
| | 1402 | sage: I.gens() |
| | 1403 | [x, y] |
| | 1404 | sage: I.gens.is_in_cache() |
| | 1405 | True |
| | 1406 | |
| | 1407 | """ |
| | 1408 | return self.cache is not None |
| | 1409 | |
| | 1410 | def __get__(self, inst, cls): #cls=None): |
| | 1411 | """ |
| | 1412 | Get a :class:`CachedMethodCallerNoArgs` bound to a specific |
| | 1413 | instance of the class of the cached method. |
| | 1414 | |
| | 1415 | NOTE: |
| | 1416 | |
| | 1417 | :class:`CachedMethodCallerNoArgs` has a separate ``__get__`` |
| | 1418 | since the categories framework creates and caches the |
| | 1419 | return value of ``CachedMethod.__get__`` with |
| | 1420 | ``inst==None``. |
| | 1421 | |
| | 1422 | This getter attempts to assign a bound method as an |
| | 1423 | attribute to the given instance. If this is not |
| | 1424 | possible (for example, for some extension classes), |
| | 1425 | it is attempted to find an attribute ``__cached_methods``, |
| | 1426 | and store/retrieve the bound method there. In that |
| | 1427 | way, cached methods can be implemented for extension |
| | 1428 | classes deriving from :class:`~sage.structure.parent.Parent` |
| | 1429 | and :class:`~sage.structure.element.Element`. |
| | 1430 | |
| | 1431 | TESTS: |
| | 1432 | |
| | 1433 | Due to the separate ``__get__`` method, it is possible |
| | 1434 | to define a cached method in one class and use it as |
| | 1435 | an attribute of another class. |
| | 1436 | |
| | 1437 | sage: class Foo: |
| | 1438 | ... def __init__(self, n): |
| | 1439 | ... self.__n = n |
| | 1440 | ... @cached_method |
| | 1441 | ... def f(self): |
| | 1442 | ... return self.__n^2 |
| | 1443 | ... |
| | 1444 | sage: class Bar: |
| | 1445 | ... f = Foo.f |
| | 1446 | ... |
| | 1447 | sage: b1 = Bar() |
| | 1448 | sage: b2 = Bar() |
| | 1449 | |
| | 1450 | The :class:`CachedMethod` is replaced by an instance of |
| | 1451 | :class:`CachedMethodCallerNoArgs` that is set as an |
| | 1452 | attribute. Hence, we have:: |
| | 1453 | |
| | 1454 | sage: b1.f is b1.f |
| | 1455 | True |
| | 1456 | sage: type(b1.f) |
| | 1457 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 1458 | |
| | 1459 | Any instance of ``Bar`` gets its own instance of |
| | 1460 | :class:`CachedMethodCaller``:: |
| | 1461 | |
| | 1462 | sage: b1.f is b2.f |
| | 1463 | False |
| | 1464 | |
| | 1465 | The method caller knows the instance that it belongs |
| | 1466 | to:: |
| | 1467 | |
| | 1468 | sage: Foo.f._instance is None |
| | 1469 | True |
| | 1470 | sage: b1.f._instance is b1 |
| | 1471 | True |
| | 1472 | sage: b2.f._instance is b2 |
| | 1473 | True |
| | 1474 | |
| | 1475 | """ |
| | 1476 | # This is for Parents or Elements that do not allow attribute assignment |
| | 1477 | try: |
| | 1478 | return (<dict>inst.__cached_methods).__getitem__(self.__name__) |
| | 1479 | except (AttributeError,TypeError,KeyError),msg: |
| | 1480 | pass |
| | 1481 | Caller = CachedMethodCallerNoArgs(inst, self.f, name=self.__name__) |
| | 1482 | try: |
| | 1483 | setattr(inst,self.__name__, Caller) |
| | 1484 | return Caller |
| | 1485 | except AttributeError: |
| | 1486 | pass |
| | 1487 | try: |
| | 1488 | if inst.__cached_methods is None: |
| | 1489 | inst.__cached_methods = {self.__name__ : Caller} |
| | 1490 | else: |
| | 1491 | (<dict>inst.__cached_methods).__setitem__(self.__name__, Caller) |
| | 1492 | except AttributeError,msg: |
| | 1493 | pass |
| | 1494 | return Caller |
| | 1495 | |
| | 1496 | cdef class CachedMethod(object): |
| | 1497 | """ |
| | 1498 | A decorator that creates a cached version of an instance |
| | 1499 | method of a class. |
| | 1500 | |
| | 1501 | NOTE: |
| | 1502 | |
| | 1503 | For proper behavior, the method must be a pure function |
| | 1504 | (no side effects). Arguments to the method must be hashable. |
| | 1505 | |
| | 1506 | EXAMPLES:: |
| | 1507 | |
| | 1508 | sage: class Foo(object): |
| | 1509 | ... @cached_method |
| | 1510 | ... def f(self, t, x=2): |
| | 1511 | ... print 'computing' |
| | 1512 | ... return t**x |
| | 1513 | sage: a = Foo() |
| | 1514 | |
| | 1515 | The example shows that the actual computation |
| | 1516 | takes place only once, and that the result is |
| | 1517 | identic for equivalent input:: |
| | 1518 | |
| | 1519 | sage: res = a.f(3, 2); res |
| | 1520 | computing |
| | 1521 | 9 |
| | 1522 | sage: a.f(t = 3, x = 2) is res |
| | 1523 | True |
| | 1524 | sage: a.f(3) is res |
| | 1525 | True |
| | 1526 | |
| | 1527 | Note, however, that the :class:`CachedMethod` is replaced by a |
| | 1528 | :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs` |
| | 1529 | as soon as it is bound to an instance or class:: |
| | 1530 | |
| | 1531 | sage: P.<a,b,c,d> = QQ[] |
| | 1532 | sage: I = P*[a,b] |
| | 1533 | sage: type(I.__class__.gens) |
| | 1534 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 1535 | |
| | 1536 | So, you would hardly ever see an instance of this class alive. |
| | 1537 | """ |
| | 1538 | def __init__(self, f, name=None): |
| | 1539 | """ |
| | 1540 | EXAMPLES:: |
| | 1541 | |
| | 1542 | sage: class Foo: |
| | 1543 | ... def __init__(self, x): |
| | 1544 | ... self._x = x |
| | 1545 | ... @cached_method |
| | 1546 | ... def f(self,n): |
| | 1547 | ... return self._x^n |
| | 1548 | ... @cached_method |
| | 1549 | ... def f0(self): |
| | 1550 | ... return self._x^2 |
| | 1551 | ... |
| | 1552 | sage: a = Foo(2) |
| | 1553 | sage: a.f(2) |
| | 1554 | 4 |
| | 1555 | sage: a.f0() |
| | 1556 | 4 |
| | 1557 | |
| | 1558 | The computations in method ``f`` are tried to store in a |
| | 1559 | dictionary assigned to the instance ``a``:: |
| | 1560 | |
| | 1561 | sage: hasattr(a, '_cache__f') |
| | 1562 | True |
| | 1563 | sage: a._cache__f |
| | 1564 | {((2,), ()): 4} |
| | 1565 | |
| | 1566 | As a shortcut, useful to speed up internal computations, |
| | 1567 | the same dictionary is also available as an attribute |
| | 1568 | of the ``CachedMethodCaller``:: |
| | 1569 | |
| | 1570 | sage: type(a.f) |
| | 1571 | <type 'sage.misc.cachefunc.CachedMethodCaller'> |
| | 1572 | sage: a.f.cache is a._cache__f |
| | 1573 | True |
| | 1574 | |
| | 1575 | Note that if the instance ``a`` would not accept attribute |
| | 1576 | assignment, the computations would still be cached in |
| | 1577 | ``a.f.cache``, and they would in fact be preserved when |
| | 1578 | pickling. |
| | 1579 | |
| | 1580 | The cached method ``f0`` accepts no arguments, which allows |
| | 1581 | for an improved way of caching: By an attribute of the cached |
| | 1582 | method itsel. This cache is *only* available in that way, i.e., |
| | 1583 | it is not additionally stored as an attribute of ``a``:: |
| | 1584 | |
| | 1585 | sage: type(a.f0) |
| | 1586 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 1587 | sage: a.f0.cache |
| | 1588 | 4 |
| | 1589 | sage: sorted(dir(a)) |
| | 1590 | ['__doc__', '__init__', '__module__', '_cache__f', '_x', 'f', 'f0'] |
| | 1591 | |
| | 1592 | """ |
| | 1593 | self._cache_name = '_cache__' + (name or f.__name__) |
| | 1594 | self._cachedfunc = CachedFunction(f, classmethod=True, name=name) |
| | 1595 | |
| | 1596 | def _instance_call(self, inst, *args, **kwds): |
| | 1597 | """ |
| | 1598 | Call the cached method *without* using the cache. |
| | 1599 | |
| | 1600 | INPUT: |
| | 1601 | |
| | 1602 | - ``inst`` - an instance at which the method is to be called |
| | 1603 | - Further positional or named arguments. |
| | 1604 | |
| | 1605 | EXAMPLES:: |
| | 1606 | |
| | 1607 | sage: class Foo(object): |
| | 1608 | ... def __init__(self, x): |
| | 1609 | ... self._x = x |
| | 1610 | ... @cached_method |
| | 1611 | ... def f(self,n=2): |
| | 1612 | ... return self._x^n |
| | 1613 | ... |
| | 1614 | sage: a = Foo(2) |
| | 1615 | sage: a.f() |
| | 1616 | 4 |
| | 1617 | |
| | 1618 | Usually, a cached method is indeed cached:: |
| | 1619 | |
| | 1620 | sage: a.f() is a.f() |
| | 1621 | True |
| | 1622 | |
| | 1623 | However, when it becomes necessary, one can call it without |
| | 1624 | using the cache. Note that ``a.f`` is an instance of |
| | 1625 | :class:`CachedMethodCaller`. But its |
| | 1626 | :meth:`CachedMethodCaller._instance_call` relies on this |
| | 1627 | method, so, we have an indirect doctest:: |
| | 1628 | |
| | 1629 | sage: a.f._instance_call() is a.f() # indirect doctest |
| | 1630 | False |
| | 1631 | sage: a.f._instance_call() == a.f() |
| | 1632 | True |
| | 1633 | |
| | 1634 | """ |
| | 1635 | return self._cachedfunc.f(inst, *args, **kwds) |
| | 1636 | |
| | 1637 | cpdef dict _get_instance_cache(self, inst): |
| | 1638 | """ |
| | 1639 | Returns the cache dictionary. |
| | 1640 | |
| | 1641 | TESTS:: |
| | 1642 | |
| | 1643 | sage: class Foo: |
| | 1644 | ... def __init__(self, x): |
| | 1645 | ... self._x = x |
| | 1646 | ... @cached_method |
| | 1647 | ... def f(self,n=2): |
| | 1648 | ... return self._x^n |
| | 1649 | ... |
| | 1650 | sage: a = Foo(2) |
| | 1651 | sage: a.f() |
| | 1652 | 4 |
| | 1653 | |
| | 1654 | Note that we can not provide a direct test, since ``a.f`` is |
| | 1655 | an instance of :class:`CachedMethodCaller`. But during its |
| | 1656 | initialisation, this method was called in order to provide the |
| | 1657 | cached method caller with its cache, and, if possible, assign |
| | 1658 | it to an attribute of ``a``. So, the following is an indirect |
| | 1659 | doctest:: |
| | 1660 | |
| | 1661 | sage: a.f.get_cache() # indirect doctest |
| | 1662 | {((2,), ()): 4} |
| | 1663 | sage: a._cache__f |
| | 1664 | {((2,), ()): 4} |
| | 1665 | |
| | 1666 | """ |
| | 1667 | try: |
| | 1668 | return inst.__dict__.setdefault(self._cache_name, {}) |
| | 1669 | except AttributeError: |
| | 1670 | return {} |
| | 1671 | |
| | 1672 | def __get__(self, inst, cls): #cls=None): |
| | 1673 | """ |
| | 1674 | Get a CachedMethodCaller bound to this specific instance of |
| | 1675 | the class of the cached method. |
| | 1676 | |
| | 1677 | TESTS:: |
| | 1678 | |
| | 1679 | sage: class Foo: |
| | 1680 | ... @cached_method |
| | 1681 | ... def f(self): |
| | 1682 | ... return 1 |
| | 1683 | ... @cached_method |
| | 1684 | ... def g(self, x,n=3): |
| | 1685 | ... return x^n |
| | 1686 | ... |
| | 1687 | sage: a = Foo() |
| | 1688 | sage: type(a.f) |
| | 1689 | <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> |
| | 1690 | sage: type(a.g) |
| | 1691 | <type 'sage.misc.cachefunc.CachedMethodCaller'> |
| | 1692 | |
| | 1693 | By trac ticket #8611, it is attempted to set the |
| | 1694 | CachedMethodCaller as an attribute of the instance ``a``, |
| | 1695 | replacing the original cached attribute. Therefore, the |
| | 1696 | ``__get__`` method will be used only once, which saves much |
| | 1697 | time. Hence, we have:: |
| | 1698 | |
| | 1699 | sage: a.f is a.f |
| | 1700 | True |
| | 1701 | sage: a.g is a.g |
| | 1702 | True |
| | 1703 | |
| | 1704 | """ |
| | 1705 | # This is for Parents or Elements that do not allow attribute assignment: |
| | 1706 | try: |
| | 1707 | name = self._cachedfunc.__name__ |
| | 1708 | except AttributeError: |
| | 1709 | name = self.__name__ |
| | 1710 | try: |
| | 1711 | return (<dict>inst.__cached_methods).__getitem__(name) |
| | 1712 | except (AttributeError,TypeError,KeyError),msg: |
| | 1713 | pass |
| | 1714 | # Apparently we need to construct the caller. |
| | 1715 | # Since we have an optimized version for functions that do not accept arguments, |
| | 1716 | # we need to analyse the argspec |
| | 1717 | f = (<CachedFunction>self._cachedfunc).f |
| | 1718 | from sage.misc.sageinspect import sage_getargspec |
| | 1719 | args, varargs, keywords, defaults = sage_getargspec(f) |
| | 1720 | if varargs is None and keywords is None and len(args)<=1: |
| | 1721 | Caller = CachedMethodCallerNoArgs(inst, f, name=name) |
| | 1722 | else: |
| | 1723 | Caller = CachedMethodCaller(self, inst, |
| | 1724 | cache=self._get_instance_cache(inst), name=name) |
| | 1725 | try: |
| | 1726 | setattr(inst,name, Caller) |
| | 1727 | return Caller |
| | 1728 | except AttributeError: |
| | 1729 | pass |
| | 1730 | try: |
| | 1731 | if inst.__cached_methods is None: |
| | 1732 | inst.__cached_methods = {name : Caller} |
| | 1733 | else: |
| | 1734 | (<dict>inst.__cached_methods).__setitem__(name, Caller) |
| | 1735 | except AttributeError: |
| | 1736 | pass |
| | 1737 | return Caller |
| | 1738 | |
| | 1739 | # Note: a simpler approach to this would be |
| | 1740 | # def caller(*args, **kwds): |
| | 1741 | # return self._instance_call(inst, *args, **kwds) |
| | 1742 | # return caller |
| | 1743 | # The disadvantage to this is that it does not provide |
| | 1744 | # is_in_cache(), set_cache(), clear_cache(), ... methods. |
| | 1745 | |
| | 1746 | |
| | 1747 | cached_method = CachedMethod |
| | 1748 | |
| | 1749 | cdef class CachedInParentMethod(CachedMethod): |
| | 1750 | r""" |
| | 1751 | A decorator that creates a cached version of an instance |
| | 1752 | method of a class. |
| | 1753 | |
| | 1754 | In contrast to :class:`CachedMethod`, |
| | 1755 | the cache dictionary is an attribute of the parent of |
| | 1756 | the instance to which the method belongs. |
| | 1757 | |
| | 1758 | ASSUMPTION: |
| | 1759 | |
| | 1760 | This way of caching works only if |
| | 1761 | |
| | 1762 | - the instances *have* a parent, and |
| | 1763 | - the instances are hashable (they are part of the cache key). |
| | 1764 | |
| | 1765 | NOTE: |
| | 1766 | |
| | 1767 | For proper behavior, the method must be a pure function (no side |
| | 1768 | effects). If this decorator is used on a method, it will have |
| | 1769 | identical output on equal elements. This is since the element is |
| | 1770 | part of the hash key. Arguments to the method and the instance |
| | 1771 | it is assigned to must be hashable. |
| | 1772 | |
| | 1773 | Examples can be found at :mod:`~sage.misc.cachefunc`. |
| | 1774 | |
| | 1775 | """ |
| | 1776 | |
| | 1777 | def __init__(self, f, name=None): |
| | 1778 | """ |
| | 1779 | Constructs a new method with cache stored in the parent of the instance. |
| | 1780 | |
| | 1781 | See also ``cached_method`` and ``cached_function``. |
| | 1782 | |
| | 1783 | EXAMPLES:: |
| | 1784 | |
| | 1785 | sage: class MyParent(Parent): |
| | 1786 | ... pass |
| | 1787 | sage: class Foo: |
| | 1788 | ... def __init__(self, x): |
| | 1789 | ... self._x = x |
| | 1790 | ... _parent = MyParent() |
| | 1791 | ... def parent(self): |
| | 1792 | ... return self._parent |
| | 1793 | ... @cached_in_parent_method #indirect doctest |
| | 1794 | ... def f(self): |
| | 1795 | ... return self._x^2 |
| | 1796 | ... |
| | 1797 | sage: a = Foo(2) |
| | 1798 | sage: a.f() |
| | 1799 | 4 |
| | 1800 | sage: hasattr(a.parent(), '_cache__element_f') |
| | 1801 | True |
| | 1802 | |
| | 1803 | For speeding up internal computations, this dictionary |
| | 1804 | is also accessible as an attribute of the CachedMethodCaller |
| | 1805 | (by trac ticket #8611):: |
| | 1806 | |
| | 1807 | sage: a.parent()._cache__element_f is a.f.cache |
| | 1808 | True |
| | 1809 | """ |
| | 1810 | self._cache_name = '_cache__' + 'element_' + (name or f.__name__) |
| | 1811 | self._cachedfunc = CachedFunction(f, classmethod=True, name=name) |
| | 1812 | |
| | 1813 | cpdef dict _get_instance_cache(self, inst): |
| | 1814 | """ |
| | 1815 | Returns the cache dictionary, which is stored in the parent. |
| | 1816 | |
| | 1817 | EXAMPLES:: |
| | 1818 | |
| | 1819 | sage: class MyParent(Parent): |
| | 1820 | ... pass |
| | 1821 | ... |
| | 1822 | sage: class Foo: |
| | 1823 | ... def __init__(self, x): |
| | 1824 | ... self._x = x |
| | 1825 | ... _parent = MyParent() |
| | 1826 | ... def parent(self): |
| | 1827 | ... return self._parent |
| | 1828 | ... def __eq__(self, other): |
| | 1829 | ... return self._x^2 == other._x^2 |
| | 1830 | ... def __hash__(self): |
| | 1831 | ... return hash(self._x^2) |
| | 1832 | ... def __repr__(self): |
| | 1833 | ... return 'My %s'%self._x |
| | 1834 | ... @cached_in_parent_method |
| | 1835 | ... def f(self): |
| | 1836 | ... return self._x^3 |
| | 1837 | ... |
| | 1838 | sage: a = Foo(2) |
| | 1839 | sage: a.f() |
| | 1840 | 8 |
| | 1841 | sage: a.f.get_cache() #indirect doctest |
| | 1842 | {(My 2, ((), ())): 8} |
| | 1843 | |
| | 1844 | Since the key for the cache depends on equality of |
| | 1845 | the instances, we obtain *identical* result for |
| | 1846 | *equal* instance - even though in this particular |
| | 1847 | example the result is wrong:: |
| | 1848 | |
| | 1849 | sage: b = Foo(-2) |
| | 1850 | sage: a is not b |
| | 1851 | True |
| | 1852 | sage: a == b |
| | 1853 | True |
| | 1854 | sage: b.f() is a.f() |
| | 1855 | True |
| | 1856 | |
| | 1857 | Non-equal instances do not share the result of |
| | 1858 | the cached method, but they do share the cache:: |
| | 1859 | |
| | 1860 | sage: c = Foo(3) |
| | 1861 | sage: c.f() |
| | 1862 | 27 |
| | 1863 | sage: c.f.get_cache() is a.f.get_cache() #indirect doctest |
| | 1864 | True |
| | 1865 | |
| | 1866 | Note that the cache is also available as an |
| | 1867 | attribute of the cached method, which speeds |
| | 1868 | up internal computations:: |
| | 1869 | |
| | 1870 | sage: a.f.cache is b.f.get_cache() is c.f._cachedmethod._get_instance_cache(c) |
| | 1871 | True |
| | 1872 | |
| | 1873 | """ |
| | 1874 | if inst is None: |
| | 1875 | return {} |
| | 1876 | try: |
| | 1877 | P = inst.parent() |
| | 1878 | return P.__dict__.setdefault(self._cache_name, {}) |
| | 1879 | except AttributeError: |
| | 1880 | pass |
| | 1881 | if not hasattr(P,'__cached_methods'): |
| | 1882 | raise TypeError, "The parent of this element does not allow attribute assignment\n and does not descend from the Parent base class.\n Can not use CachedInParentMethod." |
| | 1883 | if P.__cached_methods is None: |
| | 1884 | P.__cached_methods = {} |
| | 1885 | return (<dict>P.__cached_methods).setdefault(self._cache_name, {}) |
| | 1886 | |
| | 1887 | def __get__(self, inst, cls): #cls=None): |
| | 1888 | """ |
| | 1889 | Get a CachedMethodCaller bound to this specific instance of |
| | 1890 | the class of the cached-in-parent method. |
| | 1891 | """ |
| | 1892 | Caller = CachedMethodCaller(self, inst, cache=self._get_instance_cache(inst), inst_in_key=True) |
| | 1893 | try: |
| | 1894 | setattr(inst,self._cachedfunc.__name__, Caller) |
| | 1895 | except AttributeError: |
| | 1896 | pass |
| | 1897 | return Caller |
| | 1898 | |
| | 1899 | cached_in_parent_method = CachedInParentMethod |
| | 1900 | |
| | 1901 | class FileCache: |
| | 1902 | """ |
| | 1903 | FileCache is a dictionary-like class which stores keys and |
| | 1904 | values on disk. The keys take the form of a tuple (A,K) |
| | 1905 | |
| | 1906 | - A is a tuple of objects t where each t is an exact |
| | 1907 | object which is uniquely identified by a short string. |
| | 1908 | |
| | 1909 | - K is a tuple of tuples (s,v) where s is a valid |
| | 1910 | variable name and v is an exact object which is uniquely |
| | 1911 | identified by a short string with letters [a-zA-Z0-9-._] |
| | 1912 | |
| | 1913 | The primary use case is the DiskCachedFunction. If |
| | 1914 | ``memory_cache == True``, we maintain a cache of objects seen |
| | 1915 | during this session in memory -- but we don't load them from |
| | 1916 | disk until necessary. The keys and values are stored in a |
| | 1917 | pair of files: |
| | 1918 | |
| | 1919 | - ``prefix-argstring.key.sobj`` contains the ``key`` only, |
| | 1920 | - ``prefix-argstring.sobj`` contains the tuple ``(key,val)`` |
| | 1921 | |
| | 1922 | where ``self[key] == val``. |
| | 1923 | |
| | 1924 | NOTE: |
| | 1925 | |
| | 1926 | We assume that each FileCache lives in its own directory. |
| | 1927 | Use **extreme** caution if you wish to break that assumption. |
| | 1928 | """ |
| | 1929 | def __init__(self, dir, prefix = '', memory_cache = False): |
| | 1930 | """ |
| | 1931 | EXAMPLES:: |
| | 1932 | |
| | 1933 | sage: from sage.misc.cachefunc import FileCache |
| | 1934 | sage: dir = tmp_dir() |
| | 1935 | sage: FC = FileCache(dir, memory_cache = True) |
| | 1936 | sage: FC[((),())] = 1 |
| | 1937 | sage: FC[((1,2),())] = 2 |
| | 1938 | sage: FC[((),())] |
| | 1939 | 1 |
| | 1940 | """ |
| | 1941 | if len(dir) == 0 or dir[-1] != '/': |
| | 1942 | dir += '/' |
| | 1943 | self._dir = dir |
| | 1944 | if not os.path.exists(dir): |
| | 1945 | os.mkdir(dir) |
| | 1946 | |
| | 1947 | self._prefix = prefix + '-' |
| | 1948 | |
| | 1949 | if memory_cache: |
| | 1950 | self._cache = {} |
| | 1951 | else: |
| | 1952 | self._cache = None |
| | 1953 | |
| | 1954 | def file_list(self): |
| | 1955 | """ |
| | 1956 | Returns the list of files corresponding to self. |
| | 1957 | |
| | 1958 | EXAMPLES:: |
| | 1959 | |
| | 1960 | sage: from sage.misc.cachefunc import FileCache |
| | 1961 | sage: dir = tmp_dir() |
| | 1962 | sage: FC = FileCache(dir, memory_cache = True, prefix='t') |
| | 1963 | sage: FC[((),())] = 1 |
| | 1964 | sage: FC[((1,2),())] = 2 |
| | 1965 | sage: FC[((1,),(('a',1),))] = 3 |
| | 1966 | sage: for f in sorted(FC.file_list()): print f[len(dir):] |
| | 1967 | /t-.key.sobj |
| | 1968 | /t-.sobj |
| | 1969 | /t-1_2.key.sobj |
| | 1970 | /t-1_2.sobj |
| | 1971 | /t-a-1.1.key.sobj |
| | 1972 | /t-a-1.1.sobj |
| | 1973 | """ |
| | 1974 | files = [] |
| | 1975 | prefix = self._prefix |
| | 1976 | dir = self._dir |
| | 1977 | l = len(prefix) |
| | 1978 | for f in os.listdir(dir): |
| | 1979 | if f[:l] == prefix: |
| | 1980 | files.append( dir + f ) |
| | 1981 | return files |
| | 1982 | |
| | 1983 | def items(self): |
| | 1984 | """ |
| | 1985 | Returns a list of tuples ``(k,v)`` where ``self[k] = v``. |
| | 1986 | |
| | 1987 | EXAMPLES:: |
| | 1988 | |
| | 1989 | sage: from sage.misc.cachefunc import FileCache |
| | 1990 | sage: dir = tmp_dir() |
| | 1991 | sage: FC = FileCache(dir, memory_cache = False) |
| | 1992 | sage: FC[((),())] = 1 |
| | 1993 | sage: FC[((1,2),())] = 2 |
| | 1994 | sage: FC[((1,),(('a',1),))] = 3 |
| | 1995 | sage: I = FC.items() |
| | 1996 | sage: I.sort(); print I |
| | 1997 | [(((), ()), 1), (((1,), (('a', 1),)), 3), (((1, 2), ()), 2)] |
| | 1998 | """ |
| | 1999 | return [(k,self[k]) for k in self] |
| | 2000 | |
| | 2001 | def values(self): |
| | 2002 | """ |
| | 2003 | Returns a list of values that are stored in ``self``. |
| | 2004 | |
| | 2005 | EXAMPLES:: |
| | 2006 | |
| | 2007 | sage: from sage.misc.cachefunc import FileCache |
| | 2008 | sage: dir = tmp_dir() |
| | 2009 | sage: FC = FileCache(dir, memory_cache = False) |
| | 2010 | sage: FC[((),())] = 1 |
| | 2011 | sage: FC[((1,2),())] = 2 |
| | 2012 | sage: FC[((1,),(('a',1),))] = 3 |
| | 2013 | sage: FC[((),(('a',1),))] = 4 |
| | 2014 | sage: v = FC.values() |
| | 2015 | sage: v.sort(); print v |
| | 2016 | [1, 2, 3, 4] |
| | 2017 | """ |
| | 2018 | return [self[k] for k in self] |
| | 2019 | |
| | 2020 | def __iter__(self): |
| | 2021 | """ |
| | 2022 | Returns a list of keys of ``self``. |
| | 2023 | |
| | 2024 | EXAMPLES:: |
| | 2025 | |
| | 2026 | sage: from sage.misc.cachefunc import FileCache |
| | 2027 | sage: dir = tmp_dir() |
| | 2028 | sage: FC = FileCache(dir, memory_cache = False) |
| | 2029 | sage: FC[((),())] = 1 |
| | 2030 | sage: FC[((1,2),())] = 2 |
| | 2031 | sage: FC[((1,),(('a',1),))] = 3 |
| | 2032 | sage: for k in sorted(FC): print k |
| | 2033 | ((), ()) |
| | 2034 | ((1,), (('a', 1),)) |
| | 2035 | ((1, 2), ()) |
| | 2036 | """ |
| | 2037 | return self.keys().__iter__() |
| | 2038 | |
| | 2039 | def keys(self): |
| | 2040 | """ |
| | 2041 | Returns a list of keys ``k`` where ``self[k]`` is defined. |
| | 2042 | |
| | 2043 | EXAMPLES:: |
| | 2044 | |
| | 2045 | sage: from sage.misc.cachefunc import FileCache |
| | 2046 | sage: dir = tmp_dir() |
| | 2047 | sage: FC = FileCache(dir, memory_cache = False) |
| | 2048 | sage: FC[((),())] = 1 |
| | 2049 | sage: FC[((1,2),())] = 2 |
| | 2050 | sage: FC[((1,),(('a',1),))] = 3 |
| | 2051 | sage: K = FC.keys() |
| | 2052 | sage: K.sort(); print K |
| | 2053 | [((), ()), ((1,), (('a', 1),)), ((1, 2), ())] |
| | 2054 | """ |
| | 2055 | cdef list K = [] |
| | 2056 | from sage.structure.sage_object import load |
| | 2057 | for f in self.file_list(): |
| | 2058 | if f[-9:] == '.key.sobj': |
| | 2059 | K.append(load(f)) |
| | 2060 | return K |
| | 2061 | |
| | 2062 | def _filename(self, key): |
| | 2063 | """ |
| | 2064 | Computes the filename associated with a certain key. |
| | 2065 | |
| | 2066 | EXAMPLES:: |
| | 2067 | |
| | 2068 | sage: from sage.misc.cachefunc import FileCache |
| | 2069 | sage: dir = tmp_dir() + '/' |
| | 2070 | sage: FC = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2071 | sage: N = FC._filename(((1,2), (('a',1),('b',2)))) |
| | 2072 | sage: print N[len(dir):] |
| | 2073 | foo-a-1_b-2.1_2 |
| | 2074 | sage: N = FC._filename(((), (('a',1),('b',2)))) |
| | 2075 | sage: print N[len(dir):] |
| | 2076 | foo-a-1_b-2 |
| | 2077 | sage: N = FC._filename(((1,2), ())) |
| | 2078 | sage: print N[len(dir):] |
| | 2079 | foo-1_2 |
| | 2080 | """ |
| | 2081 | a,k = key |
| | 2082 | kwdstr = '_'.join(['%s-%s'%x for x in k]) |
| | 2083 | argstr = '_'.join(['%s'%x for x in a]) |
| | 2084 | if kwdstr and argstr: |
| | 2085 | keystr = kwdstr + '.' + argstr |
| | 2086 | else: |
| | 2087 | keystr = kwdstr + argstr |
| | 2088 | return self._dir + self._prefix + keystr |
| | 2089 | |
| | 2090 | def has_key(self, key): |
| | 2091 | """ |
| | 2092 | Returns ``True`` if ``self[key]`` is defined and False otherwise. |
| | 2093 | |
| | 2094 | EXAMPLES:: |
| | 2095 | |
| | 2096 | sage: from sage.misc.cachefunc import FileCache |
| | 2097 | sage: dir = tmp_dir() + '/' |
| | 2098 | sage: FC = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2099 | sage: k = ((),(('a',1),)) |
| | 2100 | sage: FC[k] = True |
| | 2101 | sage: FC.has_key(k) |
| | 2102 | True |
| | 2103 | sage: FC.has_key(((),())) |
| | 2104 | False |
| | 2105 | """ |
| | 2106 | return os.path.exists(self._filename(key) + '.key.sobj') |
| | 2107 | |
| | 2108 | def __getitem__(self, key): |
| | 2109 | """ |
| | 2110 | Returns the value set by ``self[key] = val``, in this session |
| | 2111 | or a previous one. |
| | 2112 | |
| | 2113 | EXAMPLES:: |
| | 2114 | |
| | 2115 | sage: from sage.misc.cachefunc import FileCache |
| | 2116 | sage: dir = tmp_dir() + '/' |
| | 2117 | sage: FC1 = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2118 | sage: FC2 = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2119 | sage: k = ((),(('a',1),)) |
| | 2120 | sage: t = randint(0, 1000) |
| | 2121 | sage: FC1[k] = t |
| | 2122 | sage: FC2[k] == FC1[k] == t |
| | 2123 | True |
| | 2124 | sage: FC1[(1,2),(('a',4),('b',2))] |
| | 2125 | Traceback (most recent call last): |
| | 2126 | ... |
| | 2127 | KeyError: ((1, 2), (('a', 4), ('b', 2))) |
| | 2128 | |
| | 2129 | """ |
| | 2130 | from sage.structure.sage_object import load |
| | 2131 | |
| | 2132 | cache = self._cache |
| | 2133 | if cache is not None: |
| | 2134 | if cache.has_key(key): |
| | 2135 | return cache[key] |
| | 2136 | |
| | 2137 | f = self._filename(key) + '.sobj' |
| | 2138 | try: |
| | 2139 | k,v = load(f) |
| | 2140 | except IOError: |
| | 2141 | raise KeyError, key |
| | 2142 | if k != key: |
| | 2143 | raise RuntimeError, "cache corrupted" |
| | 2144 | |
| | 2145 | if cache is not None: |
| | 2146 | cache[key] = v |
| | 2147 | return v |
| | 2148 | |
| | 2149 | def __setitem__(self, key, value): |
| | 2150 | """ |
| | 2151 | Sets ``self[key] = value`` and stores both key and value on |
| | 2152 | disk. |
| | 2153 | |
| | 2154 | EXAMPLES:: |
| | 2155 | |
| | 2156 | sage: from sage.misc.cachefunc import FileCache |
| | 2157 | sage: dir = tmp_dir() + '/' |
| | 2158 | sage: FC1 = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2159 | sage: FC2 = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2160 | sage: k = ((),(('a',1),)) |
| | 2161 | sage: t = randint(0, 1000) |
| | 2162 | sage: FC1[k] = t |
| | 2163 | sage: FC2[k] == t |
| | 2164 | True |
| | 2165 | sage: FC1[k] = 2000 |
| | 2166 | sage: FC2[k]!= t |
| | 2167 | True |
| | 2168 | """ |
| | 2169 | from sage.structure.sage_object import save |
| | 2170 | |
| | 2171 | f = self._filename(key) |
| | 2172 | |
| | 2173 | save(key, f+'.key.sobj') |
| | 2174 | save((key,value), f + '.sobj') |
| | 2175 | if self._cache is not None: |
| | 2176 | self._cache[key] = value |
| | 2177 | |
| | 2178 | def __delitem__(self, key): |
| | 2179 | """ |
| | 2180 | Delete the key,value pair from self and unlink the associated |
| | 2181 | files from the file cache. |
| | 2182 | |
| | 2183 | EXAMPLES:: |
| | 2184 | |
| | 2185 | sage: from sage.misc.cachefunc import FileCache |
| | 2186 | sage: dir = tmp_dir() + '/' |
| | 2187 | sage: FC1 = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2188 | sage: FC2 = FileCache(dir, memory_cache = False, prefix='foo') |
| | 2189 | sage: k = ((),(('a',1),)) |
| | 2190 | sage: t = randint(0, 1000) |
| | 2191 | sage: FC1[k] = t |
| | 2192 | sage: del FC2[k] |
| | 2193 | sage: FC1.has_key(k) |
| | 2194 | False |
| | 2195 | """ |
| | 2196 | f = self._filename(key) |
| | 2197 | cache = self._cache |
| | 2198 | if cache is not None and cache.has_key(key): |
| | 2199 | del self._cache[key] |
| | 2200 | if os.path.exists(f + '.sobj'): |
| | 2201 | os.remove(f + '.sobj') |
| | 2202 | if os.path.exists(f + '.key.sobj'): |
| | 2203 | os.remove(f + '.key.sobj') |
| | 2204 | |
| | 2205 | |
| | 2206 | class DiskCachedFunction(CachedFunction): |
| | 2207 | """ |
| | 2208 | Works similar to CachedFunction, but instead, we keep the |
| | 2209 | cache on disk (optionally, we keep it in memory too). |
| | 2210 | |
| | 2211 | EXAMPLES:: |
| | 2212 | |
| | 2213 | sage: from sage.misc.cachefunc import DiskCachedFunction |
| | 2214 | sage: dir = tmp_dir() |
| | 2215 | sage: factor = DiskCachedFunction(factor, dir, memory_cache=True) |
| | 2216 | sage: f = factor(2775); f |
| | 2217 | 3 * 5^2 * 37 |
| | 2218 | sage: f is factor(2775) |
| | 2219 | True |
| | 2220 | """ |
| | 2221 | def __init__(self, f, dir, memory_cache=False): |
| | 2222 | """ |
| | 2223 | EXAMPLES:: |
| | 2224 | |
| | 2225 | sage: from sage.misc.cachefunc import DiskCachedFunction |
| | 2226 | sage: def foo(x): sleep(x) |
| | 2227 | sage: dir = tmp_dir() |
| | 2228 | sage: bar = DiskCachedFunction(foo, dir, memory_cache = False) |
| | 2229 | sage: w = walltime() |
| | 2230 | sage: for i in range(10): bar(1) |
| | 2231 | sage: walltime(w) < 2 |
| | 2232 | True |
| | 2233 | """ |
| | 2234 | CachedFunction.__init__(self, f) |
| | 2235 | prefix = f.__name__ |
| | 2236 | self.cache = FileCache(dir, prefix=prefix, memory_cache = memory_cache) |
| | 2237 | |
| | 2238 | |
| | 2239 | class disk_cached_function: |
| | 2240 | """ |
| | 2241 | Decorator for :class:`DiskCachedFunction`. |
| | 2242 | |
| | 2243 | EXAMPLES:: |
| | 2244 | |
| | 2245 | sage: dir = tmp_dir() |
| | 2246 | sage: @disk_cached_function(dir) |
| | 2247 | ... def foo(x): return next_prime(2^x)%x |
| | 2248 | sage: x = foo(200);x |
| | 2249 | 11 |
| | 2250 | sage: @disk_cached_function(dir) |
| | 2251 | ... def foo(x): return 1/x |
| | 2252 | sage: foo(200) |
| | 2253 | 11 |
| | 2254 | sage: foo.clear_cache() |
| | 2255 | sage: foo(200) |
| | 2256 | 1/200 |
| | 2257 | """ |
| | 2258 | def __init__(self, dir, memory_cache = False): |
| | 2259 | """ |
| | 2260 | EXAMPLES:: |
| | 2261 | |
| | 2262 | sage: dir = tmp_dir() |
| | 2263 | sage: @disk_cached_function(dir, memory_cache=True) |
| | 2264 | ... def foo(x): return next_prime(2^x) |
| | 2265 | sage: x = foo(200) |
| | 2266 | sage: x is foo(200) |
| | 2267 | True |
| | 2268 | sage: @disk_cached_function(dir, memory_cache=False) |
| | 2269 | ... def foo(x): return next_prime(2^x) |
| | 2270 | sage: x is foo(200) |
| | 2271 | False |
| | 2272 | """ |
| | 2273 | self._dir = dir |
| | 2274 | self._memory_cache = memory_cache |
| | 2275 | |
| | 2276 | def __call__(self, f): |
| | 2277 | """ |
| | 2278 | EXAMPLES:: |
| | 2279 | |
| | 2280 | sage: dir = tmp_dir() |
| | 2281 | sage: @disk_cached_function(dir) |
| | 2282 | ... def foo(x): return ModularSymbols(x) |
| | 2283 | sage: foo(389) |
| | 2284 | Modular Symbols space of dimension 65 for Gamma_0(389) of weight 2 with sign 0 over Rational Field |
| | 2285 | """ |
| | 2286 | return DiskCachedFunction(f, self._dir, memory_cache = self._memory_cache) |
| | 2287 | |
| | 2288 | class ClearCacheOnPickle(object): |
| | 2289 | r""" |
| | 2290 | This class implements an appropriate ``__getstate__`` method that |
| | 2291 | clears the cache of the methods (see @cached_method) before |
| | 2292 | passing them on to the caller, typically the pickle and copy modules. |
| | 2293 | |
| | 2294 | The implemented ``__getstate__`` method calls the ``__getstate__`` |
| | 2295 | methods of classes later in the method resolution |
| | 2296 | order. Therefore, classes which want this behaviour should inherit |
| | 2297 | first from this one. |
| | 2298 | |
| | 2299 | EXAMPLE: |
| | 2300 | |
| | 2301 | In the following example, we create a Python class that inherits |
| | 2302 | from multivariate polynomial ideals, but does not pickle cached |
| | 2303 | values. We provide the definition in Cython, however, since |
| | 2304 | interactive Cython definitions provide introspection by trac |
| | 2305 | ticket #9976, whereas Python definitions don't. |
| | 2306 | :: |
| | 2307 | |
| | 2308 | sage: P.<a,b,c,d> = QQ[] |
| | 2309 | sage: I = P*[a,b] |
| | 2310 | sage: classdef = ['from sage.misc.cachefunc import ClearCacheOnPickle', |
| | 2311 | ... 'from sage.all import QQ', |
| | 2312 | ... 'P = QQ["a","b","c","d"]; I = P*[P.gen(0),P.gen(1)]', |
| | 2313 | ... 'class MyClass(ClearCacheOnPickle,I.__class__):', |
| | 2314 | ... ' def __init__(self,ring,gens):', |
| | 2315 | ... ' I.__class__.__init__(self,ring,gens)', |
| | 2316 | ... ' def __getnewargs__(self):', |
| | 2317 | ... ' return (self._Ideal_generic__ring,self._Ideal_generic__gens)'] |
| | 2318 | sage: cython('\n'.join(classdef)) |
| | 2319 | |
| | 2320 | We destroy the cache of two methods of ``I`` on purpose |
| | 2321 | (demonstrating that the two different implementations of cached |
| | 2322 | methods are correctly dealt with). Pickling ``I`` preserves the |
| | 2323 | cache:: |
| | 2324 | |
| | 2325 | sage: I.gens.set_cache('bar') |
| | 2326 | sage: I.groebner_basis.set_cache('foo',algorithm='singular') |
| | 2327 | sage: J = loads(dumps(I)) |
| | 2328 | sage: J.gens() |
| | 2329 | 'bar' |
| | 2330 | sage: J.groebner_basis(algorithm='singular') |
| | 2331 | 'foo' |
| | 2332 | |
| | 2333 | However, if we have an ideal that additionally descends from |
| | 2334 | :class:`ClearCacheOnPickle`, the carefully corrupted cache is not |
| | 2335 | pickled:: |
| | 2336 | |
| | 2337 | sage: A = MyClass(P,[a,b]) |
| | 2338 | sage: A |
| | 2339 | Ideal (a, b) of Multivariate Polynomial Ring in a, b, c, d over Rational Field |
| | 2340 | sage: A.gens.set_cache('foo') |
| | 2341 | sage: A.groebner_basis.set_cache('bar',algorithm='singular') |
| | 2342 | sage: A.gens() |
| | 2343 | 'foo' |
| | 2344 | sage: A.groebner_basis(algorithm='singular') |
| | 2345 | 'bar' |
| | 2346 | sage: B = loads(dumps(A)) |
| | 2347 | sage: B.gens() |
| | 2348 | [a, b] |
| | 2349 | sage: B.groebner_basis(algorithm='singular') |
| | 2350 | [a, b] |
| | 2351 | sage: A.gens() |
| | 2352 | 'foo' |
| | 2353 | |
| | 2354 | """ |
| | 2355 | def __getstate__(self): |
| | 2356 | r""" |
| | 2357 | The idea is to remove that might provide a cache to some cached method |
| | 2358 | from the return value of the ``__getstate__`` method. |
| | 2359 | |
| | 2360 | EXAMPLE: |
| | 2361 | |
| | 2362 | In the following example, we create a Python class that |
| | 2363 | inherits from multivariate polynomial ideals, but clears the |
| | 2364 | cache as well. |
| | 2365 | |
| | 2366 | sage: P.<a,b,c,d> = QQ[] |
| | 2367 | sage: I = P*[a,b] |
| | 2368 | |
| | 2369 | We destroy the cache of two methods if ``I`` on purpose |
| | 2370 | (demonstrating that the two different implementations of cached |
| | 2371 | methods are correctly dealt with). Pickling ``I`` preserves the |
| | 2372 | cache:: |
| | 2373 | |
| | 2374 | sage: I.gens.set_cache('bar') |
| | 2375 | sage: I.groebner_basis.set_cache('foo',algorithm='singular') |
| | 2376 | sage: J = loads(dumps(I)) |
| | 2377 | sage: J.gens() |
| | 2378 | 'bar' |
| | 2379 | sage: J.groebner_basis(algorithm='singular') |
| | 2380 | 'foo' |
| | 2381 | |
| | 2382 | However, if we do the same with a class that additionally |
| | 2383 | descends from :class:`ClearCacheOnPickle`, the cache is not |
| | 2384 | pickled. We provide the definition in Cython, however, since |
| | 2385 | interactive Cython definitions provide introspection by trac |
| | 2386 | ticket #9976, whereas Python definitions don't. |
| | 2387 | :: |
| | 2388 | |
| | 2389 | sage: classdef = ['from sage.misc.cachefunc import ClearCacheOnPickle', |
| | 2390 | ... 'from sage.all import QQ', |
| | 2391 | ... 'from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal', |
| | 2392 | ... 'class MyClass(ClearCacheOnPickle,MPolynomialIdeal):', |
| | 2393 | ... ' def __init__(self,ring,gens):', |
| | 2394 | ... ' MPolynomialIdeal.__init__(self,ring,gens)', |
| | 2395 | ... ' def __getnewargs__(self):', |
| | 2396 | ... ' return (self._Ideal_generic__ring,self._Ideal_generic__gens)'] |
| | 2397 | sage: cython('\n'.join(classdef)) |
| | 2398 | sage: A = MyClass(P,[a,b]) |
| | 2399 | sage: A |
| | 2400 | Ideal (a, b) of Multivariate Polynomial Ring in a, b, c, d over Rational Field |
| | 2401 | sage: A.gens.set_cache('foo') |
| | 2402 | sage: A.groebner_basis.set_cache('bar',algorithm='singular') |
| | 2403 | sage: A.gens() |
| | 2404 | 'foo' |
| | 2405 | sage: A.groebner_basis(algorithm='singular') |
| | 2406 | 'bar' |
| | 2407 | sage: B = loads(dumps(A)) |
| | 2408 | sage: B.gens() |
| | 2409 | [a, b] |
| | 2410 | sage: B.groebner_basis(algorithm='singular') |
| | 2411 | [a, b] |
| | 2412 | sage: A.gens() |
| | 2413 | 'foo' |
| | 2414 | |
| | 2415 | And here is why the example works:: |
| | 2416 | |
| | 2417 | sage: ST = I.__getstate__(); ST[0],sorted(ST[1].items()) |
| | 2418 | (Monoid of ideals of Multivariate Polynomial Ring in a, b, c, d over Rational Field, [('_Ideal_generic__gens', (a, b)), ('_Ideal_generic__ring', Multivariate Polynomial Ring in a, b, c, d over Rational Field), ('_cache__groebner_basis', {(('singular', None, None, False), ()): 'foo'}), ('gens', Cached version of <function gens at 0x...>), ('groebner_basis', Cached version of <function groebner_basis at 0x...>)]) |
| | 2419 | sage: ST = A.__getstate__(); ST[0],sorted(ST[1].items()) |
| | 2420 | (Monoid of ideals of Multivariate Polynomial Ring in a, b, c, d over Rational Field, [('_Ideal_generic__gens', (a, b)), ('_Ideal_generic__ring', Multivariate Polynomial Ring in a, b, c, d over Rational Field)]) |
| | 2421 | |
| | 2422 | """ |
| | 2423 | OrigState = super(ClearCacheOnPickle, self).__getstate__() |
| | 2424 | def clear_list(T): |
| | 2425 | L = [] |
| | 2426 | for x in T: |
| | 2427 | if isinstance(x,list): |
| | 2428 | L.append(clear_list(x)) |
| | 2429 | elif isinstance(x,tuple): |
| | 2430 | L.append(clear_tuple(x)) |
| | 2431 | elif isinstance(x,dict): |
| | 2432 | L.append(clear_dict(x)) |
| | 2433 | elif not isinstance(x,CachedFunction): |
| | 2434 | L.append(x) |
| | 2435 | return L |
| | 2436 | def clear_tuple(T): |
| | 2437 | return tuple(clear_list(T)) |
| | 2438 | def clear_dict(T): |
| | 2439 | D = {} |
| | 2440 | for key,value in T.iteritems(): |
| | 2441 | if not ((type(key) == str and key[0:8] == '_cache__') or |
| | 2442 | isinstance(value,CachedFunction)): |
| | 2443 | if isinstance(value,list): |
| | 2444 | D[key] = clear_list(value) |
| | 2445 | elif isinstance(value,tuple): |
| | 2446 | D[key] = clear_tuple(value) |
| | 2447 | elif isinstance(value,dict): |
| | 2448 | D[key] = clear_dict(value) |
| | 2449 | else: |
| | 2450 | D[key] = value |
| | 2451 | return D |
| | 2452 | if isinstance(OrigState,tuple): |
| | 2453 | return clear_tuple(OrigState) |
| | 2454 | if isinstance(OrigState,list): |
| | 2455 | return clear_list(OrigState) |
| | 2456 | if isinstance(OrigState,dict): |
| | 2457 | return clear_dict(OrigState) |
| | 2458 | return OrigState |