~ubuntu-branches/ubuntu/natty/pytables/natty-updates

« back to all changes in this revision

Viewing changes to tables/lrucache.py

  • Committer: Bazaar Package Importer
  • Author(s): Alexandre Fayolle
  • Date: 2006-06-28 10:45:03 UTC
  • mfrom: (1.2.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 5.
  • Revision ID: james.westby@ubuntu.com-20060628104503-cc251q5o5j3e2k10
  * Fixed call to pyversions in debian/rules which failed on recent versions 
    of pyversions
  * Fixed clean rule in debian/rules which left the stamp files behind
  * Acknowledge NMU
  * Added Alexandre Fayolle to uploaders

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# lrucache.py -- a simple LRU (Least-Recently-Used) cache class
 
2
 
 
3
# Copyright 2004 Evan Prodromou <evan@bad.dynu.ca>
 
4
# Licensed under the Academic Free License 2.1
 
5
 
 
6
# Modified to use monotonically increasing integer values as access times
 
7
# by Ivan Vilata i Balaguer <ivilata@carabos.com>.
 
8
 
 
9
# Modified to use a container in Pyrex for accelerating the heapify()
 
10
# process by Francesc Altet <faltet@carabos.com>.
 
11
 
 
12
# arch-tag: LRU cache main module
 
13
 
 
14
"""a simple LRU (Least-Recently-Used) cache module
 
15
 
 
16
This module provides very simple LRU (Least-Recently-Used) cache
 
17
functionality.
 
18
 
 
19
An *in-memory cache* is useful for storing the results of an
 
20
'expensive' process (one that takes a lot of time or resources) for
 
21
later re-use. Typical examples are accessing data from the filesystem,
 
22
a database, or a network location. If you know you'll need to re-read
 
23
the data again, it can help to keep it in a cache.
 
24
 
 
25
You *can* use a Python dictionary as a cache for some purposes.
 
26
However, if the results you're caching are large, or you have a lot of
 
27
possible results, this can be impractical memory-wise.
 
28
 
 
29
An *LRU cache*, on the other hand, only keeps _some_ of the results in
 
30
memory, which keeps you from overusing resources. The cache is bounded
 
31
by a maximum size; if you try to add more values to the cache, it will
 
32
automatically discard the values that you haven't read or written to
 
33
in the longest time. In other words, the least-recently-used items are
 
34
discarded. [1]_
 
35
 
 
36
.. [1]: 'Discarded' here means 'removed from the cache'.
 
37
 
 
38
"""
 
39
 
 
40
from __future__ import generators
 
41
import time
 
42
from heapq import heappush, heappop, heapify
 
43
from utilsExtension import _g_Node
 
44
 
 
45
__version__ = "0.2"
 
46
__all__ = ['CacheKeyError', 'LRUCache', 'DEFAULT_SIZE']
 
47
__docformat__ = 'reStructuredText en'
 
48
 
 
49
DEFAULT_SIZE = 16
 
50
"""Default size of a new LRUCache object, if no 'size' argument is given."""
 
51
 
 
52
class CacheKeyError(KeyError):
 
53
    """Error raised when cache requests fail
 
54
 
 
55
    When a cache record is accessed which no longer exists (or never did),
 
56
    this error is raised. To avoid it, you may want to check for the existence
 
57
    of a cache record before reading or deleting it."""
 
58
    pass
 
59
 
 
60
class LRUCache(object):
 
61
    """Least-Recently-Used (LRU) cache.
 
62
 
 
63
    Instances of this class provide a least-recently-used (LRU) cache. They
 
64
    emulate a Python mapping type. You can use an LRU cache more or less like
 
65
    a Python dictionary, with the exception that objects you put into the
 
66
    cache may be discarded before you take them out.
 
67
 
 
68
    Some example usage::
 
69
 
 
70
    cache = LRUCache(32) # new cache
 
71
    cache['foo'] = get_file_contents('foo') # or whatever
 
72
 
 
73
    if 'foo' in cache: # if it's still in cache...
 
74
            # use cached version
 
75
        contents = cache['foo']
 
76
    else:
 
77
            # recalculate
 
78
        contents = get_file_contents('foo')
 
79
            # store in cache for next time
 
80
        cache['foo'] = contents
 
81
 
 
82
    print cache.size # Maximum size
 
83
 
 
84
    print len(cache) # 0 <= len(cache) <= cache.size
 
85
 
 
86
    cache.size = 10 # Auto-shrink on size assignment
 
87
 
 
88
    for i in range(50): # note: larger than cache size
 
89
        cache[i] = i
 
90
 
 
91
    if 0 not in cache: print 'Zero was discarded.'
 
92
 
 
93
    if 42 in cache:
 
94
        del cache[42] # Manual deletion
 
95
 
 
96
    for j in cache:   # iterate (in LRU order)
 
97
        print j, cache[j] # iterator produces keys, not values
 
98
    """
 
99
 
 
100
    class __Node(object):
 
101
        """Record of a cached value. Not for public consumption."""
 
102
 
 
103
        def __init__(self, key, obj, timestamp):
 
104
            object.__init__(self)
 
105
            self.key = key
 
106
            self.obj = obj
 
107
            self.atime = timestamp
 
108
            #self.mtime = time.time()
 
109
 
 
110
        def __cmp__(self, other):
 
111
            return cmp(self.atime, other.atime)
 
112
 
 
113
        def __repr__(self):
 
114
            return "<%s %s => %s (accessed at %s)>" % \
 
115
                   (self.__class__, self.key, self.obj, self.atime)
 
116
 
 
117
    def __getseqn(self):
 
118
        seqn = self.__seqn_
 
119
        self.__seqn_ = seqn + 1
 
120
        return seqn
 
121
 
 
122
    __seqn = property(__getseqn)
 
123
 
 
124
    def __init__(self, size=DEFAULT_SIZE):
 
125
        # Check arguments
 
126
        if size <= 0:
 
127
            raise ValueError, size
 
128
        elif type(size) is not type(0):
 
129
            raise TypeError, size
 
130
        object.__init__(self)
 
131
        self.__heap = []
 
132
        self.__dict = {}
 
133
        self.__seqn_ = 0
 
134
        self.size = size
 
135
        """Maximum size of the cache.
 
136
        If more than 'size' elements are added to the cache,
 
137
        the least-recently-used ones will be discarded."""
 
138
 
 
139
    def __len__(self):
 
140
        return len(self.__heap)
 
141
 
 
142
    def __contains__(self, key):
 
143
        return self.__dict.has_key(key)
 
144
 
 
145
    def __setitem__(self, key, obj):
 
146
        if self.__dict.has_key(key):
 
147
            node = self.__dict[key]
 
148
            node.obj = obj
 
149
            node.atime = self.__seqn
 
150
            heapify(self.__heap)
 
151
        else:
 
152
            # size may have been reset, so we loop
 
153
            while len(self.__heap) >= self.size:
 
154
                lru = heappop(self.__heap)
 
155
                del self.__dict[lru.key]
 
156
            # Using _g_Node ext. is almost 4x faster than __Node in pure python
 
157
            node = self.__Node(key, obj, self.__seqn)
 
158
            #node = _g_Node(key, obj, self.__seqn)
 
159
            self.__dict[key] = node
 
160
            heappush(self.__heap, node)
 
161
 
 
162
    def __getitem__(self, key):
 
163
        if not self.__dict.has_key(key):
 
164
            raise CacheKeyError(key)
 
165
        else:
 
166
            node = self.__dict[key]
 
167
            node.atime = self.__seqn
 
168
            heapify(self.__heap)
 
169
            return node.obj
 
170
 
 
171
    def __delitem__(self, key):
 
172
        if not self.__dict.has_key(key):
 
173
            raise CacheKeyError(key)
 
174
        else:
 
175
            node = self.__dict[key]
 
176
            del self.__dict[key]
 
177
            self.__heap.remove(node)
 
178
            heapify(self.__heap)
 
179
            return node.obj
 
180
 
 
181
    def __iter__(self):
 
182
        copy = self.__heap[:]
 
183
        while len(copy) > 0:
 
184
            node = heappop(copy)
 
185
            yield node.key
 
186
        raise StopIteration
 
187
 
 
188
    def __setattr__(self, name, value):
 
189
        object.__setattr__(self, name, value)
 
190
        # automagically shrink heap on resize
 
191
        if name == 'size':
 
192
            while len(self.__heap) > value:
 
193
                lru = heappop(self.__heap)
 
194
                del self.__dict[lru.key]
 
195
 
 
196
    def __repr__(self):
 
197
        return "<%s (%d elements)>" % (str(self.__class__), len(self.__heap))
 
198
 
 
199
    def mtime(self, key):
 
200
        """Return the last modification time for the cache record with key.
 
201
        May be useful for cache instances where the stored values can get
 
202
        'stale', such as caching file or network resource contents."""
 
203
        if not self.__dict.has_key(key):
 
204
            raise CacheKeyError(key)
 
205
        else:
 
206
            node = self.__dict[key]
 
207
            return node.mtime
 
208
 
 
209
if __name__ == "__main__":
 
210
    cache = LRUCache(25)
 
211
    print cache
 
212
    for i in range(50):
 
213
        cache[i] = str(i)
 
214
    print cache
 
215
    if 46 in cache:
 
216
        del cache[46]
 
217
    print cache
 
218
    cache.size = 10
 
219
    print cache
 
220
    cache[46] = '46'
 
221
    print cache
 
222
    print len(cache)
 
223
    for c in cache:
 
224
        print c
 
225
    print cache
 
226
    print cache.mtime(46)
 
227
    for c in cache:
 
228
        print c