Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/boltons/cacheutils.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/boltons/cacheutils.py Thu May 14 16:47:39 2020 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,847 +0,0 @@ -# -*- coding: utf-8 -*- -"""``cacheutils`` contains consistent implementations of fundamental -cache types. Currently there are two to choose from: - - * :class:`LRI` - Least-recently inserted - * :class:`LRU` - Least-recently used - -Both caches are :class:`dict` subtypes, designed to be as -interchangeable as possible, to facilitate experimentation. A key -practice with performance enhancement with caching is ensuring that -the caching strategy is working. If the cache is constantly missing, -it is just adding more overhead and code complexity. The standard -statistics are: - - * ``hit_count`` - the number of times the queried key has been in - the cache - * ``miss_count`` - the number of times a key has been absent and/or - fetched by the cache - * ``soft_miss_count`` - the number of times a key has been absent, - but a default has been provided by the caller, as with - :meth:`dict.get` and :meth:`dict.setdefault`. Soft misses are a - subset of misses, so this number is always less than or equal to - ``miss_count``. - -Additionally, ``cacheutils`` provides :class:`ThresholdCounter`, a -cache-like bounded counter useful for online statistics collection. - -Learn more about `caching algorithms on Wikipedia -<https://en.wikipedia.org/wiki/Cache_algorithms#Examples>`_. - -""" - -# TODO: TimedLRI -# TODO: support 0 max_size? - - -import heapq -import weakref -import itertools -from operator import attrgetter - -try: - from threading import RLock -except Exception: - class RLock(object): - 'Dummy reentrant lock for builds without threads' - def __enter__(self): - pass - - def __exit__(self, exctype, excinst, exctb): - pass - -try: - from boltons.typeutils import make_sentinel - _MISSING = make_sentinel(var_name='_MISSING') - _KWARG_MARK = make_sentinel(var_name='_KWARG_MARK') -except ImportError: - _MISSING = object() - _KWARG_MARK = object() - -try: - xrange -except NameError: - # py3 - xrange = range - unicode, str, bytes, basestring = str, bytes, bytes, (str, bytes) - -PREV, NEXT, KEY, VALUE = range(4) # names for the link fields -DEFAULT_MAX_SIZE = 128 - - -class LRI(dict): - """The ``LRI`` implements the basic *Least Recently Inserted* strategy to - caching. One could also think of this as a ``SizeLimitedDefaultDict``. - - *on_miss* is a callable that accepts the missing key (as opposed - to :class:`collections.defaultdict`'s "default_factory", which - accepts no arguments.) Also note that, like the :class:`LRI`, - the ``LRI`` is instrumented with statistics tracking. - - >>> cap_cache = LRI(max_size=2) - >>> cap_cache['a'], cap_cache['b'] = 'A', 'B' - >>> from pprint import pprint as pp - >>> pp(dict(cap_cache)) - {'a': 'A', 'b': 'B'} - >>> [cap_cache['b'] for i in range(3)][0] - 'B' - >>> cap_cache['c'] = 'C' - >>> print(cap_cache.get('a')) - None - >>> cap_cache.hit_count, cap_cache.miss_count, cap_cache.soft_miss_count - (3, 1, 1) - """ - def __init__(self, max_size=DEFAULT_MAX_SIZE, values=None, - on_miss=None): - if max_size <= 0: - raise ValueError('expected max_size > 0, not %r' % max_size) - self.hit_count = self.miss_count = self.soft_miss_count = 0 - self.max_size = max_size - self._lock = RLock() - self._init_ll() - - if on_miss is not None and not callable(on_miss): - raise TypeError('expected on_miss to be a callable' - ' (or None), not %r' % on_miss) - self.on_miss = on_miss - - if values: - self.update(values) - - # TODO: fromkeys()? - - # linked list manipulation methods. - # - # invariants: - # 1) 'anchor' is the sentinel node in the doubly linked list. there is - # always only one, and its KEY and VALUE are both _MISSING. - # 2) the most recently accessed node comes immediately before 'anchor'. - # 3) the least recently accessed node comes immediately after 'anchor'. - def _init_ll(self): - anchor = [] - anchor[:] = [anchor, anchor, _MISSING, _MISSING] - # a link lookup table for finding linked list links in O(1) - # time. - self._link_lookup = {} - self._anchor = anchor - - def _print_ll(self): - print('***') - for (key, val) in self._get_flattened_ll(): - print(key, val) - print('***') - return - - def _get_flattened_ll(self): - flattened_list = [] - link = self._anchor - while True: - flattened_list.append((link[KEY], link[VALUE])) - link = link[NEXT] - if link is self._anchor: - break - return flattened_list - - def _get_link_and_move_to_front_of_ll(self, key): - # find what will become the newest link. this may raise a - # KeyError, which is useful to __getitem__ and __setitem__ - newest = self._link_lookup[key] - - # splice out what will become the newest link. - newest[PREV][NEXT] = newest[NEXT] - newest[NEXT][PREV] = newest[PREV] - - # move what will become the newest link immediately before - # anchor (invariant 2) - anchor = self._anchor - second_newest = anchor[PREV] - second_newest[NEXT] = anchor[PREV] = newest - newest[PREV] = second_newest - newest[NEXT] = anchor - return newest - - def _set_key_and_add_to_front_of_ll(self, key, value): - # create a new link and place it immediately before anchor - # (invariant 2). - anchor = self._anchor - second_newest = anchor[PREV] - newest = [second_newest, anchor, key, value] - second_newest[NEXT] = anchor[PREV] = newest - self._link_lookup[key] = newest - - def _set_key_and_evict_last_in_ll(self, key, value): - # the link after anchor is the oldest in the linked list - # (invariant 3). the current anchor becomes a link that holds - # the newest key, and the oldest link becomes the new anchor - # (invariant 1). now the newest link comes before anchor - # (invariant 2). no links are moved; only their keys - # and values are changed. - oldanchor = self._anchor - oldanchor[KEY] = key - oldanchor[VALUE] = value - - self._anchor = anchor = oldanchor[NEXT] - evicted = anchor[KEY] - anchor[KEY] = anchor[VALUE] = _MISSING - del self._link_lookup[evicted] - self._link_lookup[key] = oldanchor - return evicted - - def _remove_from_ll(self, key): - # splice a link out of the list and drop it from our lookup - # table. - link = self._link_lookup.pop(key) - link[PREV][NEXT] = link[NEXT] - link[NEXT][PREV] = link[PREV] - - def __setitem__(self, key, value): - with self._lock: - try: - link = self._get_link_and_move_to_front_of_ll(key) - except KeyError: - if len(self) < self.max_size: - self._set_key_and_add_to_front_of_ll(key, value) - else: - evicted = self._set_key_and_evict_last_in_ll(key, value) - super(LRI, self).__delitem__(evicted) - super(LRI, self).__setitem__(key, value) - else: - link[VALUE] = value - - def __getitem__(self, key): - with self._lock: - try: - link = self._link_lookup[key] - except KeyError: - self.miss_count += 1 - if not self.on_miss: - raise - ret = self[key] = self.on_miss(key) - return ret - - self.hit_count += 1 - return link[VALUE] - - def get(self, key, default=None): - try: - return self[key] - except KeyError: - self.soft_miss_count += 1 - return default - - def __delitem__(self, key): - with self._lock: - super(LRI, self).__delitem__(key) - self._remove_from_ll(key) - - def pop(self, key, default=_MISSING): - # NB: hit/miss counts are bypassed for pop() - with self._lock: - try: - ret = super(LRI, self).pop(key) - except KeyError: - if default is _MISSING: - raise - ret = default - else: - self._remove_from_ll(key) - return ret - - def popitem(self): - with self._lock: - item = super(LRI, self).popitem() - self._remove_from_ll(item[0]) - return item - - def clear(self): - with self._lock: - super(LRI, self).clear() - self._init_ll() - - def copy(self): - return self.__class__(max_size=self.max_size, values=self) - - def setdefault(self, key, default=None): - with self._lock: - try: - return self[key] - except KeyError: - self.soft_miss_count += 1 - self[key] = default - return default - - def update(self, E, **F): - # E and F are throwback names to the dict() __doc__ - with self._lock: - if E is self: - return - setitem = self.__setitem__ - if callable(getattr(E, 'keys', None)): - for k in E.keys(): - setitem(k, E[k]) - else: - for k, v in E: - setitem(k, v) - for k in F: - setitem(k, F[k]) - return - - def __eq__(self, other): - with self._lock: - if self is other: - return True - if len(other) != len(self): - return False - if not isinstance(other, LRI): - return other == self - return super(LRI, self).__eq__(other) - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - cn = self.__class__.__name__ - val_map = super(LRI, self).__repr__() - return ('%s(max_size=%r, on_miss=%r, values=%s)' - % (cn, self.max_size, self.on_miss, val_map)) - - -class LRU(LRI): - """The ``LRU`` is :class:`dict` subtype implementation of the - *Least-Recently Used* caching strategy. - - Args: - max_size (int): Max number of items to cache. Defaults to ``128``. - values (iterable): Initial values for the cache. Defaults to ``None``. - on_miss (callable): a callable which accepts a single argument, the - key not present in the cache, and returns the value to be cached. - - >>> cap_cache = LRU(max_size=2) - >>> cap_cache['a'], cap_cache['b'] = 'A', 'B' - >>> from pprint import pprint as pp - >>> pp(dict(cap_cache)) - {'a': 'A', 'b': 'B'} - >>> [cap_cache['b'] for i in range(3)][0] - 'B' - >>> cap_cache['c'] = 'C' - >>> print(cap_cache.get('a')) - None - - This cache is also instrumented with statistics - collection. ``hit_count``, ``miss_count``, and ``soft_miss_count`` - are all integer members that can be used to introspect the - performance of the cache. ("Soft" misses are misses that did not - raise :exc:`KeyError`, e.g., ``LRU.get()`` or ``on_miss`` was used to - cache a default. - - >>> cap_cache.hit_count, cap_cache.miss_count, cap_cache.soft_miss_count - (3, 1, 1) - - Other than the size-limiting caching behavior and statistics, - ``LRU`` acts like its parent class, the built-in Python :class:`dict`. - """ - def __getitem__(self, key): - with self._lock: - try: - link = self._get_link_and_move_to_front_of_ll(key) - except KeyError: - self.miss_count += 1 - if not self.on_miss: - raise - ret = self[key] = self.on_miss(key) - return ret - - self.hit_count += 1 - return link[VALUE] - - -### Cached decorator -# Key-making technique adapted from Python 3.4's functools - -class _HashedKey(list): - """The _HashedKey guarantees that hash() will be called no more than once - per cached function invocation. - """ - __slots__ = 'hash_value' - - def __init__(self, key): - self[:] = key - self.hash_value = hash(tuple(key)) - - def __hash__(self): - return self.hash_value - - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, list.__repr__(self)) - - -def make_cache_key(args, kwargs, typed=False, - kwarg_mark=_KWARG_MARK, - fasttypes=frozenset([int, str, frozenset, type(None)])): - """Make a generic key from a function's positional and keyword - arguments, suitable for use in caches. Arguments within *args* and - *kwargs* must be `hashable`_. If *typed* is ``True``, ``3`` and - ``3.0`` will be treated as separate keys. - - The key is constructed in a way that is flat as possible rather than - as a nested structure that would take more memory. - - If there is only a single argument and its data type is known to cache - its hash value, then that argument is returned without a wrapper. This - saves space and improves lookup speed. - - >>> tuple(make_cache_key(('a', 'b'), {'c': ('d')})) - ('a', 'b', _KWARG_MARK, ('c', 'd')) - - .. _hashable: https://docs.python.org/2/glossary.html#term-hashable - """ - - # key = [func_name] if func_name else [] - # key.extend(args) - key = list(args) - if kwargs: - sorted_items = sorted(kwargs.items()) - key.append(kwarg_mark) - key.extend(sorted_items) - if typed: - key.extend([type(v) for v in args]) - if kwargs: - key.extend([type(v) for k, v in sorted_items]) - elif len(key) == 1 and type(key[0]) in fasttypes: - return key[0] - return _HashedKey(key) - -# for backwards compatibility in case someone was importing it -_make_cache_key = make_cache_key - - -class CachedFunction(object): - """This type is used by :func:`cached`, below. Instances of this - class are used to wrap functions in caching logic. - """ - def __init__(self, func, cache, scoped=True, typed=False, key=None): - self.func = func - if callable(cache): - self.get_cache = cache - elif not (callable(getattr(cache, '__getitem__', None)) - and callable(getattr(cache, '__setitem__', None))): - raise TypeError('expected cache to be a dict-like object,' - ' or callable returning a dict-like object, not %r' - % cache) - else: - def _get_cache(): - return cache - self.get_cache = _get_cache - self.scoped = scoped - self.typed = typed - self.key_func = key or make_cache_key - - def __call__(self, *args, **kwargs): - cache = self.get_cache() - key = self.key_func(args, kwargs, typed=self.typed) - try: - ret = cache[key] - except KeyError: - ret = cache[key] = self.func(*args, **kwargs) - return ret - - def __repr__(self): - cn = self.__class__.__name__ - if self.typed or not self.scoped: - return ("%s(func=%r, scoped=%r, typed=%r)" - % (cn, self.func, self.scoped, self.typed)) - return "%s(func=%r)" % (cn, self.func) - - -class CachedMethod(object): - """Similar to :class:`CachedFunction`, this type is used by - :func:`cachedmethod` to wrap methods in caching logic. - """ - def __init__(self, func, cache, scoped=True, typed=False, key=None): - self.func = func - self.__isabstractmethod__ = getattr(func, '__isabstractmethod__', False) - if isinstance(cache, basestring): - self.get_cache = attrgetter(cache) - elif callable(cache): - self.get_cache = cache - elif not (callable(getattr(cache, '__getitem__', None)) - and callable(getattr(cache, '__setitem__', None))): - raise TypeError('expected cache to be an attribute name,' - ' dict-like object, or callable returning' - ' a dict-like object, not %r' % cache) - else: - def _get_cache(obj): - return cache - self.get_cache = _get_cache - self.scoped = scoped - self.typed = typed - self.key_func = key or make_cache_key - self.bound_to = None - - def __get__(self, obj, objtype=None): - if obj is None: - return self - cls = self.__class__ - ret = cls(self.func, self.get_cache, typed=self.typed, - scoped=self.scoped, key=self.key_func) - ret.bound_to = obj - return ret - - def __call__(self, *args, **kwargs): - obj = args[0] if self.bound_to is None else self.bound_to - cache = self.get_cache(obj) - key_args = (self.bound_to, self.func) + args if self.scoped else args - key = self.key_func(key_args, kwargs, typed=self.typed) - try: - ret = cache[key] - except KeyError: - if self.bound_to is not None: - args = (self.bound_to,) + args - ret = cache[key] = self.func(*args, **kwargs) - return ret - - def __repr__(self): - cn = self.__class__.__name__ - args = (cn, self.func, self.scoped, self.typed) - if self.bound_to is not None: - args += (self.bound_to,) - return ('<%s func=%r scoped=%r typed=%r bound_to=%r>' % args) - return ("%s(func=%r, scoped=%r, typed=%r)" % args) - - -def cached(cache, scoped=True, typed=False, key=None): - """Cache any function with the cache object of your choosing. Note - that the function wrapped should take only `hashable`_ arguments. - - Args: - cache (Mapping): Any :class:`dict`-like object suitable for - use as a cache. Instances of the :class:`LRU` and - :class:`LRI` are good choices, but a plain :class:`dict` - can work in some cases, as well. This argument can also be - a callable which accepts no arguments and returns a mapping. - scoped (bool): Whether the function itself is part of the - cache key. ``True`` by default, different functions will - not read one another's cache entries, but can evict one - another's results. ``False`` can be useful for certain - shared cache use cases. More advanced behavior can be - produced through the *key* argument. - typed (bool): Whether to factor argument types into the cache - check. Default ``False``, setting to ``True`` causes the - cache keys for ``3`` and ``3.0`` to be considered unequal. - - >>> my_cache = LRU() - >>> @cached(my_cache) - ... def cached_lower(x): - ... return x.lower() - ... - >>> cached_lower("CaChInG's FuN AgAiN!") - "caching's fun again!" - >>> len(my_cache) - 1 - - .. _hashable: https://docs.python.org/2/glossary.html#term-hashable - - """ - def cached_func_decorator(func): - return CachedFunction(func, cache, scoped=scoped, typed=typed, key=key) - return cached_func_decorator - - -def cachedmethod(cache, scoped=True, typed=False, key=None): - """Similar to :func:`cached`, ``cachedmethod`` is used to cache - methods based on their arguments, using any :class:`dict`-like - *cache* object. - - Args: - cache (str/Mapping/callable): Can be the name of an attribute - on the instance, any Mapping/:class:`dict`-like object, or - a callable which returns a Mapping. - scoped (bool): Whether the method itself and the object it is - bound to are part of the cache keys. ``True`` by default, - different methods will not read one another's cache - results. ``False`` can be useful for certain shared cache - use cases. More advanced behavior can be produced through - the *key* arguments. - typed (bool): Whether to factor argument types into the cache - check. Default ``False``, setting to ``True`` causes the - cache keys for ``3`` and ``3.0`` to be considered unequal. - key (callable): A callable with a signature that matches - :func:`make_cache_key` that returns a tuple of hashable - values to be used as the key in the cache. - - >>> class Lowerer(object): - ... def __init__(self): - ... self.cache = LRI() - ... - ... @cachedmethod('cache') - ... def lower(self, text): - ... return text.lower() - ... - >>> lowerer = Lowerer() - >>> lowerer.lower('WOW WHO COULD GUESS CACHING COULD BE SO NEAT') - 'wow who could guess caching could be so neat' - >>> len(lowerer.cache) - 1 - - """ - def cached_method_decorator(func): - return CachedMethod(func, cache, scoped=scoped, typed=typed, key=key) - return cached_method_decorator - - -class cachedproperty(object): - """The ``cachedproperty`` is used similar to :class:`property`, except - that the wrapped method is only called once. This is commonly used - to implement lazy attributes. - - After the property has been accessed, the value is stored on the - instance itself, using the same name as the cachedproperty. This - allows the cache to be cleared with :func:`delattr`, or through - manipulating the object's ``__dict__``. - """ - def __init__(self, func): - self.__doc__ = getattr(func, '__doc__') - self.__isabstractmethod__ = getattr(func, '__isabstractmethod__', False) - self.func = func - - def __get__(self, obj, objtype=None): - if obj is None: - return self - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - - def __repr__(self): - cn = self.__class__.__name__ - return '<%s func=%s>' % (cn, self.func) - - -class ThresholdCounter(object): - """A **bounded** dict-like Mapping from keys to counts. The - ThresholdCounter automatically compacts after every (1 / - *threshold*) additions, maintaining exact counts for any keys - whose count represents at least a *threshold* ratio of the total - data. In other words, if a particular key is not present in the - ThresholdCounter, its count represents less than *threshold* of - the total data. - - >>> tc = ThresholdCounter(threshold=0.1) - >>> tc.add(1) - >>> tc.items() - [(1, 1)] - >>> tc.update([2] * 10) - >>> tc.get(1) - 0 - >>> tc.add(5) - >>> 5 in tc - True - >>> len(list(tc.elements())) - 11 - - As you can see above, the API is kept similar to - :class:`collections.Counter`. The most notable feature omissions - being that counted items cannot be set directly, uncounted, or - removed, as this would disrupt the math. - - Use the ThresholdCounter when you need best-effort long-lived - counts for dynamically-keyed data. Without a bounded datastructure - such as this one, the dynamic keys often represent a memory leak - and can impact application reliability. The ThresholdCounter's - item replacement strategy is fully deterministic and can be - thought of as *Amortized Least Relevant*. The absolute upper bound - of keys it will store is *(2/threshold)*, but realistically - *(1/threshold)* is expected for uniformly random datastreams, and - one or two orders of magnitude better for real-world data. - - This algorithm is an implementation of the Lossy Counting - algorithm described in "Approximate Frequency Counts over Data - Streams" by Manku & Motwani. Hat tip to Kurt Rose for discovery - and initial implementation. - - """ - # TODO: hit_count/miss_count? - def __init__(self, threshold=0.001): - if not 0 < threshold < 1: - raise ValueError('expected threshold between 0 and 1, not: %r' - % threshold) - - self.total = 0 - self._count_map = {} - self._threshold = threshold - self._thresh_count = int(1 / threshold) - self._cur_bucket = 1 - - @property - def threshold(self): - return self._threshold - - def add(self, key): - """Increment the count of *key* by 1, automatically adding it if it - does not exist. - - Cache compaction is triggered every *1/threshold* additions. - """ - self.total += 1 - try: - self._count_map[key][0] += 1 - except KeyError: - self._count_map[key] = [1, self._cur_bucket - 1] - - if self.total % self._thresh_count == 0: - self._count_map = dict([(k, v) for k, v in self._count_map.items() - if sum(v) > self._cur_bucket]) - self._cur_bucket += 1 - return - - def elements(self): - """Return an iterator of all the common elements tracked by the - counter. Yields each key as many times as it has been seen. - """ - repeaters = itertools.starmap(itertools.repeat, self.iteritems()) - return itertools.chain.from_iterable(repeaters) - - def most_common(self, n=None): - """Get the top *n* keys and counts as tuples. If *n* is omitted, - returns all the pairs. - """ - if n <= 0: - return [] - ret = sorted(self.iteritems(), key=lambda x: x[1], reverse=True) - if n is None or n >= len(ret): - return ret - return ret[:n] - - def get_common_count(self): - """Get the sum of counts for keys exceeding the configured data - threshold. - """ - return sum([count for count, _ in self._count_map.values()]) - - def get_uncommon_count(self): - """Get the sum of counts for keys that were culled because the - associated counts represented less than the configured - threshold. The long-tail counts. - """ - return self.total - self.get_common_count() - - def get_commonality(self): - """Get a float representation of the effective count accuracy. The - higher the number, the less uniform the keys being added, and - the higher accuracy and efficiency of the ThresholdCounter. - - If a stronger measure of data cardinality is required, - consider using hyperloglog. - """ - return float(self.get_common_count()) / self.total - - def __getitem__(self, key): - return self._count_map[key][0] - - def __len__(self): - return len(self._count_map) - - def __contains__(self, key): - return key in self._count_map - - def iterkeys(self): - return iter(self._count_map) - - def keys(self): - return list(self.iterkeys()) - - def itervalues(self): - count_map = self._count_map - for k in count_map: - yield count_map[k][0] - - def values(self): - return list(self.itervalues()) - - def iteritems(self): - count_map = self._count_map - for k in count_map: - yield (k, count_map[k][0]) - - def items(self): - return list(self.iteritems()) - - def get(self, key, default=0): - "Get count for *key*, defaulting to 0." - try: - return self[key] - except KeyError: - return default - - def update(self, iterable, **kwargs): - """Like dict.update() but add counts instead of replacing them, used - to add multiple items in one call. - - Source can be an iterable of keys to add, or a mapping of keys - to integer counts. - """ - if iterable is not None: - if callable(getattr(iterable, 'iteritems', None)): - for key, count in iterable.iteritems(): - for i in xrange(count): - self.add(key) - else: - for key in iterable: - self.add(key) - if kwargs: - self.update(kwargs) - - -class MinIDMap(object): - """ - Assigns arbitrary weakref-able objects the smallest possible unique - integer IDs, such that no two objects have the same ID at the same - time. - - Maps arbitrary hashable objects to IDs. - - Based on https://gist.github.com/kurtbrose/25b48114de216a5e55df - """ - def __init__(self): - self.mapping = weakref.WeakKeyDictionary() - self.ref_map = {} - self.free = [] - - def get(self, a): - try: - return self.mapping[a][0] # if object is mapped, return ID - except KeyError: - pass - - if self.free: # if there are any free IDs, use the smallest - nxt = heapq.heappop(self.free) - else: # if there are no free numbers, use the next highest ID - nxt = len(self.mapping) - ref = weakref.ref(a, self._clean) - self.mapping[a] = (nxt, ref) - self.ref_map[ref] = nxt - return nxt - - def drop(self, a): - freed, ref = self.mapping[a] - del self.mapping[a] - del self.ref_map[ref] - heapq.heappush(self.free, freed) - - def _clean(self, ref): - print(self.ref_map[ref]) - heapq.heappush(self.free, self.ref_map[ref]) - del self.ref_map[ref] - - def __contains__(self, a): - return a in self.mapping - - def __iter__(self): - return iter(self.mapping) - - def __len__(self): - return self.mapping.__len__() - - def iteritems(self): - return iter((k, self.mapping[k][0]) for k in iter(self.mapping)) - - -# end cacheutils.py