forked from cool-RR/python_toolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.txt
More file actions
97 lines (71 loc) · 3.29 KB
/
cache.txt
File metadata and controls
97 lines (71 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
..
Copyright 2009-2017 Ram Rachum. This work is licensed under a Creative
Commons Attribution-ShareAlike 3.0 Unported License, with attribution to
"Ram Rachum at ram.rachum.com" including link. The license may be obtained
at http://creativecommons.org/licenses/by-sa/3.0/
.. _topics-caching-cache:
:func:`caching.cache`
====================
A caching decorator that understands arguments
----------------------------------------------
The idea of a caching decorator is very cool. You decorate your function with a
caching decorator:
>>> from python_toolbox import caching
>>>
>>> @caching.cache
... def f(x):
... print('Calculating...')
... return x ** x # Some long expensive computation
And then, every time you call it, it'll cache the results for next time:
>>> f(4)
Calculating...
256
>>> f(5)
Calculating...
3125
>>> f(5)
3125
>>> f(5)
3125
As you can see, after the first time we calculate ``f(5)`` the result gets
saved to a cache and every time we'll call ``f(5)`` Python will return the
result from the cache instead of calculating it again. This prevents making
redundant performance-expensive calculations.
Now, depending on the function, there can be many different ways to make the same call. For example, if you have a function defined like this::
def g(a, b=2, **kwargs):
return whatever
Then ``g(1)``, ``g(1, 2)``, ``g(b=2, a=1)`` and even ``g(1, 2, **{})`` are all equivalent. They give the exact same arguments, just in different ways. Most caching decorators out there don't understand that. If you call ``g(1)`` and then ``g(1, 2)``, they will calculate the function again, because they don't understand that it's exactly the same call and they could use the cached result.
Enter :func:`caching.cache`:
>>> @caching.cache()
... def g(a, b=2, **kwargs):
... print('Calculating')
... return (a, b, kwargs)
...
>>> g(1)
Calculating
(1, 2, {})
>>> g(1, 2) # Look ma, no calculating:
(1, 2, {})
>>> g(b=2, a=1) # No calculating again:
(1, 2, {})
>>> g(1, 2, **{}) # No calculating here either:
(1, 2, {})
>>> g('something_else') # Now calculating for different arguments:
Calculating
('something_else', 2, {})
As you can see above, :func:`caching.cache` analyzes the function and understands
that calls like ``g(1)`` and ``g(1, 2)`` are identical and therefore should be
cached together.
Both limited and unlimited cache
--------------------------------
By default, the cache size will be unlimited. If you want to limit the cache size, pass in the ``max_size`` argument:
>>> @caching.cache(max_size=7)
... def f(): pass
If and when the cache size reaches the limit (7 in this case), old values will
get thrown away according to a `LRU order`_.
Sleekrefs
----------
:func:`caching.cache` arguments with sleekrefs. Sleekrefs are a more robust variation of `weakrefs`_. They are basically a gracefully-degrading version of weakrefs, so you can use them on un-weakreff-able objects like :class:`int`\, and they will just use regular references.
The usage of sleekrefs prevents memory leaks when using potentially-heavy arguments.
.. _LRU order: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
.. _weakrefs: http://docs.python.org/library/weakref.html