1 from __future__ import division
6 from twisted.internet import reactor
9 def __init__(self, contents, prev=None, next=None):
10 self.contents, self.prev, self.next = contents, prev, next
12 def insert_before(self, contents):
13 self.prev.next = self.prev = node = Node(contents, self.prev, self)
16 def insert_after(self, contents):
17 self.next.prev = self.next = node = Node(contents, self, self.next)
21 def connect(prev, next):
22 if prev.next is not None or next.prev is not None:
23 raise ValueError('node already connected')
24 prev.next, next.prev = next, prev
26 def replace(self, contents):
27 self.contents = contents
30 if self.prev.next is None or self.next.prev is None:
31 raise ValueError('node not connected')
32 self.prev.next, self.next.prev = self.next, self.prev
33 self.next = self.prev = None
36 class LinkedList(object):
37 def __init__(self, iterable=[]):
38 self.start, self.end = Node(None), Node(None)
39 Node.connect(self.start, self.end)
45 return 'LinkedList(%r)' % (list(self),)
48 return sum(1 for x in self)
52 while cur is not self.end:
55 yield cur2 # in case cur is deleted, but items inserted after are ignored
57 def __reversed__(self):
59 while cur is not self.start:
64 def __getitem__(self, index):
65 # odd one out - probably should return Node instance instead of its contents
68 for i in xrange(-index):
71 raise IndexError('index out of range')
74 for i in xrange(index + 1):
77 raise IndexError('index out of range')
80 def appendleft(self, item):
81 return self.start.insert_after(item)
83 def append(self, item):
84 return self.end.insert_before(item)
87 node = self.start.next
89 raise IndexError('popleft from empty')
95 if node is self.start:
96 raise IndexError('pop from empty')
101 class ExpiringDict(object):
102 def __init__(self, expiry_time=100, get_touches=True):
103 self.expiry_time = expiry_time
104 self.get_touches = get_touches
106 self.expiry_deque = LinkedList()
107 self.d = dict() # key -> node, value
110 reactor.callLater(0, self.expire)
111 return 'ExpiringDict' + repr(self.__dict__)
114 reactor.callLater(0, self.expire)
118 def touch(self, key, value=_nothing):
119 'Updates expiry node, optionally replacing value, returning new value'
120 if value is self._nothing or key in self.d:
121 node, old_value = self.d[key]
124 new_value = old_value if value is self._nothing else value
125 self.d[key] = self.expiry_deque.append((time.time() + random.expovariate(1/self.expiry_time), key)), new_value
130 for node in self.expiry_deque:
131 timestamp, key = node.contents
137 def __contains__(self, key):
140 def __getitem__(self, key):
142 value = self.touch(key)
144 node, value = self.d[key]
145 reactor.callLater(0, self.expire)
148 def __setitem__(self, key, value):
149 self.touch(key, value)
150 reactor.callLater(0, self.expire)
152 def __delitem__(self, key):
153 node, value = self.d.pop(key)
155 reactor.callLater(0, self.expire)
157 def get(self, key, default_value=None):
162 reactor.callLater(0, self.expire)
165 def setdefault(self, key, default_value):
169 self[key] = default_value
176 return [value for node, value in self.d.itervalues()]
178 def itervalues(self):
179 for node, value in self.d.itervalues():
182 if __name__ == '__main__':