datastructures.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. """
  2. Custom Datastructures
  3. """
  4. import time
  5. import traceback
  6. from UserList import UserList
  7. from Queue import Queue, Empty as QueueEmpty
  8. class PositionQueue(UserList):
  9. """A positional queue of a specific length, with slots that are either
  10. filled or unfilled. When all of the positions are filled, the queue
  11. is considered :meth:`full`.
  12. :param length: see :attr:`length`.
  13. .. attribute:: length
  14. The number of items required for the queue to be considered full.
  15. """
  16. class UnfilledPosition(object):
  17. """Describes an unfilled slot."""
  18. def __init__(self, position):
  19. self.position = position
  20. def __init__(self, length):
  21. self.length = length
  22. self.data = map(self.UnfilledPosition, xrange(length))
  23. def full(self):
  24. """Returns ``True`` if all of the slots has been filled."""
  25. return len(self) >= self.length
  26. def __len__(self):
  27. """``len(self)`` -> number of slots filled with real values."""
  28. return len(self.filled)
  29. @property
  30. def filled(self):
  31. """Returns the filled slots as a list."""
  32. return filter(lambda v: not isinstance(v, self.UnfilledPosition),
  33. self.data)
  34. class ExceptionInfo(object):
  35. """Exception wrapping an exception and its traceback.
  36. :param exc_info: The exception tuple info as returned by
  37. :func:`traceback.format_exception`.
  38. .. attribute:: exception
  39. The original exception.
  40. .. attribute:: traceback
  41. A traceback from the point when :attr:`exception` was raised.
  42. """
  43. def __init__(self, exc_info):
  44. type_, exception, tb = exc_info
  45. self.exception = exception
  46. self.traceback = ''.join(traceback.format_exception(*exc_info))
  47. def __str__(self):
  48. return self.traceback
  49. def __repr__(self):
  50. return "<%s.%s: %s>" % (
  51. self.__class__.__module__,
  52. self.__class__.__name__,
  53. str(self.exception))
  54. class SharedCounter(object):
  55. """Thread-safe counter.
  56. Please note that the final value is not synchronized, this means
  57. that you should not update the value by using a previous value, the only
  58. reliable operations are increment and decrement.
  59. Example
  60. >>> max_clients = SharedCounter(initial_value=10)
  61. # Thread one
  62. >>> max_clients += 1 # OK (safe)
  63. # Thread two
  64. >>> max_clients -= 3 # OK (safe)
  65. # Main thread
  66. >>> if client >= int(max_clients): # Max clients now at 8
  67. ... wait()
  68. >>> max_client = max_clients + 10 # NOT OK (unsafe)
  69. """
  70. def __init__(self, initial_value):
  71. self._value = initial_value
  72. self._modify_queue = Queue()
  73. def increment(self, n=1):
  74. """Increment value."""
  75. self += n
  76. def decrement(self, n=1):
  77. """Decrement value."""
  78. self -= n
  79. def _update_value(self):
  80. self._value += sum(consume_queue(self._modify_queue))
  81. return self._value
  82. def __iadd__(self, y):
  83. """``self += y``"""
  84. self._modify_queue.put(y * +1)
  85. return self
  86. def __isub__(self, y):
  87. """``self -= y``"""
  88. self._modify_queue.put(y * -1)
  89. return self
  90. def __int__(self):
  91. """``int(self) -> int``"""
  92. return self._update_value()
  93. def __repr__(self):
  94. return "<SharedCounter: int(%s)>" % str(int(self))
  95. class LimitedSet(object):
  96. """Kind-of Set with limitations.
  97. Good for when you need to test for membership (``a in set``),
  98. but the list might become to big, so you want to limit it so it doesn't
  99. consume too much resources.
  100. :keyword maxlen: Maximum number of members before we start
  101. deleting expired members.
  102. :keyword expires: Time in seconds, before a membership expires.
  103. """
  104. def __init__(self, maxlen=None, expires=None):
  105. self.maxlen = maxlen
  106. self.expires = expires
  107. self._data = {}
  108. def add(self, value):
  109. """Add a new member."""
  110. self._expire_item()
  111. self._data[value] = time.time()
  112. def pop_value(self, value):
  113. """Remove membership by finding value."""
  114. self._data.pop(value, None)
  115. def _expire_item(self):
  116. """Hunt down and remove an expired item."""
  117. while 1:
  118. if self.maxlen and len(self) >= self.maxlen:
  119. value, when = self.first
  120. if not self.expires or time.time() > when + self.expires:
  121. try:
  122. self.pop_value(value)
  123. except TypeError: # pragma: no cover
  124. continue
  125. break
  126. def __contains__(self, value):
  127. return value in self._data
  128. def __iter__(self):
  129. return iter(self._data.keys())
  130. def __len__(self):
  131. return len(self._data.keys())
  132. def __repr__(self):
  133. return "LimitedSet([%s])" % (repr(self._data.keys()))
  134. @property
  135. def chronologically(self):
  136. return sorted(self._data.items(), key=lambda (value, when): when)
  137. @property
  138. def first(self):
  139. """Get the oldest member."""
  140. return self.chronologically[0]
  141. def consume_queue(queue):
  142. """Iterator yielding all immediately available items in a
  143. :class:`Queue.Queue`.
  144. The iterator stops as soon as the queue raises :exc:`Queue.Empty`.
  145. Example
  146. >>> q = Queue()
  147. >>> map(q.put, range(4))
  148. >>> list(consume_queue(q))
  149. [0, 1, 2, 3]
  150. >>> list(consume_queue(q))
  151. []
  152. """
  153. while 1:
  154. try:
  155. yield queue.get_nowait()
  156. except QueueEmpty:
  157. break