1
+ import collections
1
2
import functools
2
3
3
4
__all__ = [
4
5
'apartial' ,
6
+ 'lru_cache' ,
5
7
]
6
8
7
9
@@ -14,3 +16,64 @@ def apartial(coro, *args, **kwargs):
14
16
async def wrapped (* cargs , ** ckwargs ):
15
17
return await coro (* args , * cargs , ** kwargs , ** ckwargs )
16
18
return wrapped
19
+
20
+
21
+ def lru_cache (maxsize = 128 , typed = False ):
22
+ '''
23
+ A simple LRU cache just like functools.lru_cache, but it works for
24
+ coroutines. This is not as heavily optimized as functools.lru_cache
25
+ which uses an internal C implementation, as it targets async operations
26
+ that take a long time.
27
+
28
+ It follows the same API that the standard functools provides. The wrapped
29
+ function has ``cache_clear()`` method to flush the cache manually, but
30
+ leaves ``cache_info()`` for statistics unimplemented.
31
+
32
+ Note that calling the coroutine multiple times with the same arguments
33
+ before the first call returns may incur duplicate exectuions.
34
+
35
+ This function is not thread-safe.
36
+ '''
37
+
38
+ if maxsize is not None and not isinstance (maxsize , int ):
39
+ raise TypeError ('Expected maxsize to be an integer or None' )
40
+
41
+ def wrapper (coro ):
42
+
43
+ sentinel = object () # unique object to distinguish None as result
44
+ cache = collections .OrderedDict ()
45
+ cache_get = cache .get
46
+ cache_set = cache .__setitem__
47
+ cache_len = cache .__len__
48
+ cache_move = cache .move_to_end
49
+ make_key = functools ._make_key
50
+
51
+ # We don't use explicit locks like the standard functools,
52
+ # because this lru_cache is intended for use in asyncio coroutines.
53
+ # The only context interleaving happens when calling the user-defined
54
+ # coroutine, so there is no need to add extra synchronization guards.
55
+
56
+ @functools .wraps (coro )
57
+ async def wrapped (* args , ** kwargs ):
58
+ k = make_key (args , kwargs , typed )
59
+ result = cache_get (k , sentinel )
60
+ if result is not sentinel :
61
+ return result
62
+ result = await coro (* args , ** kwargs )
63
+ if maxsize is not None and cache_len () >= maxsize :
64
+ cache .popitem (last = False )
65
+ cache_set (k , result )
66
+ cache_move (k , last = True )
67
+ return result
68
+
69
+ def cache_clear ():
70
+ cache .clear ()
71
+
72
+ def cache_info ():
73
+ raise NotImplementedError
74
+
75
+ wrapped .cache_clear = cache_clear
76
+ wrapped .cache_info = cache_info
77
+ return wrapped
78
+
79
+ return wrapper
0 commit comments