1- """
2- A fork of Python 3.6's stdlib lru_cache (found in Python's 'cpython/Lib/functools.py')
3- adapted into a data structure for single threaded uses.
4-
5- https://github.com/python/cpython/blob/v3.6.12/Lib/functools.py
6-
7-
8- Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
9- 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
10-
11- All Rights Reserved
12-
13-
14- PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
15- --------------------------------------------
16-
17- 1. This LICENSE AGREEMENT is between the Python Software Foundation
18- ("PSF"), and the Individual or Organization ("Licensee") accessing and
19- otherwise using this software ("Python") in source or binary form and
20- its associated documentation.
21-
22- 2. Subject to the terms and conditions of this License Agreement, PSF hereby
23- grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
24- analyze, test, perform and/or display publicly, prepare derivative works,
25- distribute, and otherwise use Python alone or in any derivative version,
26- provided, however, that PSF's License Agreement and PSF's notice of copyright,
27- i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
28- 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
29- All Rights Reserved" are retained in Python alone or in any derivative version
30- prepared by Licensee.
31-
32- 3. In the event Licensee prepares a derivative work that is based on
33- or incorporates Python or any part thereof, and wants to make
34- the derivative work available to others as provided herein, then
35- Licensee hereby agrees to include in any such work a brief summary of
36- the changes made to Python.
37-
38- 4. PSF is making Python available to Licensee on an "AS IS"
39- basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
40- IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
41- DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
42- FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
43- INFRINGE ANY THIRD PARTY RIGHTS.
44-
45- 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
46- FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
47- A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
48- OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
49-
50- 6. This License Agreement will automatically terminate upon a material
51- breach of its terms and conditions.
52-
53- 7. Nothing in this License Agreement shall be deemed to create any
54- relationship of agency, partnership, or joint venture between PSF and
55- Licensee. This License Agreement does not grant permission to use PSF
56- trademarks or trade name in a trademark sense to endorse or promote
57- products or services of Licensee, or any third party.
58-
59- 8. By copying, installing or otherwise using Python, Licensee
60- agrees to be bound by the terms and conditions of this License
61- Agreement.
62-
63- """
64-
65- from typing import cast , Any
66-
67- SENTINEL = object ()
68-
69-
70- # aliases to the entries in a node
71- PREV = 0
72- NEXT = 1
73- KEY = 2
74- VALUE = 3
1+ _SENTINEL = object ()
752
763
774class LRUCache :
78- def __init__ (self , max_size ):
79- assert max_size > 0
80-
5+ def __init__ (self , max_size : int ):
6+ if max_size <= 0 :
7+ raise AssertionError ( f"invalid max_size: { max_size } " )
818 self .max_size = max_size
82- self .full = False
83-
84- self .cache = {}
85-
86- # root of the circularly linked list to keep track of
87- # the least recently used key
88- self .root = [] # type: ignore
89- # the node looks like [PREV, NEXT, KEY, VALUE]
90- self .root [:] = [self .root , self .root , None , None ]
91-
9+ self ._data = {}
9210 self .hits = self .misses = 0
11+ self .full = False
9312
9413 def __copy__ (self ):
95- # walk around the circle and fill the new root / cache
96- cache = {}
97- node_old = root_old = self .root
98- node_new = root_new = [cast (Any , None )] * 4
99- while (node_old := node_old [NEXT ]) is not root_old :
100- _ , _ , key , val = node_old
101- cache [node_old [KEY ]] = node_new [NEXT ] = [node_new , None , key , val ]
102- node_new = node_new [NEXT ]
103-
104- # close the circle
105- node_new [NEXT ] = root_new
106- root_new [PREV ] = node_new
107-
108- lru_cache = LRUCache (self .max_size )
109- lru_cache .full = self .full
110- lru_cache .cache = cache
111- lru_cache .root = root_new
112- return lru_cache
14+ new = LRUCache (max_size = self .max_size )
15+ new .hits = self .hits
16+ new .misses = self .misses
17+ new .full = self .full
18+ new ._data = self ._data .copy ()
19+ return new
11320
11421 def set (self , key , value ):
115- link = self .cache .get (key , SENTINEL )
116-
117- if link is not SENTINEL :
118- # have to move the node to the front of the linked list
119- link_prev , link_next , _key , _value = link
120-
121- # first remove the node from the lsnked list
122- link_prev [NEXT ] = link_next
123- link_next [PREV ] = link_prev
124-
125- # insert the node between the root and the last
126- last = self .root [PREV ]
127- last [NEXT ] = self .root [PREV ] = link
128- link [PREV ] = last
129- link [NEXT ] = self .root
130-
131- # update the value
132- link [VALUE ] = value
133-
22+ current = self ._data .pop (key , _SENTINEL )
23+ if current is not _SENTINEL :
24+ self ._data [key ] = value
13425 elif self .full :
135- # reuse the root node, so update its key/value
136- old_root = self .root
137- old_root [KEY ] = key
138- old_root [VALUE ] = value
139-
140- self .root = old_root [NEXT ]
141- old_key = self .root [KEY ]
142-
143- self .root [KEY ] = self .root [VALUE ] = None
144-
145- del self .cache [old_key ]
146-
147- self .cache [key ] = old_root
148-
26+ self ._data .pop (next (iter (self ._data )))
27+ self ._data [key ] = value
14928 else :
150- # insert new node after last
151- last = self .root [PREV ]
152- link = [last , self .root , key , value ]
153- last [NEXT ] = self .root [PREV ] = self .cache [key ] = link
154- self .full = len (self .cache ) >= self .max_size
29+ self ._data [key ] = value
30+ self .full = len (self ._data ) >= self .max_size
15531
15632 def get (self , key , default = None ):
157- link = self . cache . get ( key , SENTINEL )
158-
159- if link is SENTINEL :
33+ try :
34+ ret = self . _data . pop ( key )
35+ except KeyError :
16036 self .misses += 1
161- return default
162-
163- # have to move the node to the front of the linked list
164- link_prev , link_next , _key , _value = link
165-
166- # first remove the node from the lsnked list
167- link_prev [NEXT ] = link_next
168- link_next [PREV ] = link_prev
169-
170- # insert the node between the root and the last
171- last = self .root [PREV ]
172- last [NEXT ] = self .root [PREV ] = link
173- link [PREV ] = last
174- link [NEXT ] = self .root
175-
176- self .hits += 1
37+ ret = default
38+ else :
39+ self .hits += 1
40+ self ._data [key ] = ret
17741
178- return link [ VALUE ]
42+ return ret
17943
18044 def get_all (self ):
181- nodes = []
182- node = self .root [NEXT ]
183-
184- while node is not self .root :
185- nodes .append ((node [KEY ], node [VALUE ]))
186- node = node [NEXT ]
187-
188- return nodes
45+ return list (self ._data .items ())
0 commit comments