1#!/usr/bin/env python 2 3# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Module which provides compatibility with older Python versions.""" 8 9import collections 10import functools 11import sys 12 13__all__ = ["PY3", "long", "xrange", "unicode", "callable", "lru_cache"] 14 15PY3 = sys.version_info[0] == 3 16 17if PY3: 18 long = int 19 xrange = range 20 unicode = str 21 22 def u(s): 23 return s 24else: 25 long = long 26 xrange = xrange 27 unicode = unicode 28 29 def u(s): 30 return unicode(s, "unicode_escape") 31 32 33# removed in 3.0, reintroduced in 3.2 34try: 35 callable = callable 36except NameError: 37 def callable(obj): 38 return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) 39 40 41# --- stdlib additions 42 43 44# py 3.2 functools.lru_cache 45# Taken from: http://code.activestate.com/recipes/578078 46# Credit: Raymond Hettinger 47try: 48 from functools import lru_cache 49except ImportError: 50 try: 51 from threading import RLock 52 except ImportError: 53 from dummy_threading import RLock 54 55 _CacheInfo = collections.namedtuple( 56 "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) 57 58 class _HashedSeq(list): 59 __slots__ = 'hashvalue' 60 61 def __init__(self, tup, hash=hash): 62 self[:] = tup 63 self.hashvalue = hash(tup) 64 65 def __hash__(self): 66 return self.hashvalue 67 68 def _make_key(args, kwds, typed, 69 kwd_mark=(object(), ), 70 fasttypes=set((int, str, frozenset, type(None))), 71 sorted=sorted, tuple=tuple, type=type, len=len): 72 key = args 73 if kwds: 74 sorted_items = sorted(kwds.items()) 75 key += kwd_mark 76 for item in sorted_items: 77 key += item 78 if typed: 79 key += tuple(type(v) for v in args) 80 if kwds: 81 key += tuple(type(v) for k, v in sorted_items) 82 elif len(key) == 1 and type(key[0]) in fasttypes: 83 return key[0] 84 return _HashedSeq(key) 85 86 def lru_cache(maxsize=100, typed=False): 87 """Least-recently-used cache decorator, see: 88 http://docs.python.org/3/library/functools.html#functools.lru_cache 89 """ 90 def decorating_function(user_function): 91 cache = dict() 92 stats = [0, 0] 93 HITS, MISSES = 0, 1 94 make_key = _make_key 95 cache_get = cache.get 96 _len = len 97 lock = RLock() 98 root = [] 99 root[:] = [root, root, None, None] 100 nonlocal_root = [root] 101 PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 102 if maxsize == 0: 103 def wrapper(*args, **kwds): 104 result = user_function(*args, **kwds) 105 stats[MISSES] += 1 106 return result 107 elif maxsize is None: 108 def wrapper(*args, **kwds): 109 key = make_key(args, kwds, typed) 110 result = cache_get(key, root) 111 if result is not root: 112 stats[HITS] += 1 113 return result 114 result = user_function(*args, **kwds) 115 cache[key] = result 116 stats[MISSES] += 1 117 return result 118 else: 119 def wrapper(*args, **kwds): 120 if kwds or typed: 121 key = make_key(args, kwds, typed) 122 else: 123 key = args 124 lock.acquire() 125 try: 126 link = cache_get(key) 127 if link is not None: 128 root, = nonlocal_root 129 link_prev, link_next, key, result = link 130 link_prev[NEXT] = link_next 131 link_next[PREV] = link_prev 132 last = root[PREV] 133 last[NEXT] = root[PREV] = link 134 link[PREV] = last 135 link[NEXT] = root 136 stats[HITS] += 1 137 return result 138 finally: 139 lock.release() 140 result = user_function(*args, **kwds) 141 lock.acquire() 142 try: 143 root, = nonlocal_root 144 if key in cache: 145 pass 146 elif _len(cache) >= maxsize: 147 oldroot = root 148 oldroot[KEY] = key 149 oldroot[RESULT] = result 150 root = nonlocal_root[0] = oldroot[NEXT] 151 oldkey = root[KEY] 152 root[KEY] = root[RESULT] = None 153 del cache[oldkey] 154 cache[key] = oldroot 155 else: 156 last = root[PREV] 157 link = [last, root, key, result] 158 last[NEXT] = root[PREV] = cache[key] = link 159 stats[MISSES] += 1 160 finally: 161 lock.release() 162 return result 163 164 def cache_info(): 165 """Report cache statistics""" 166 lock.acquire() 167 try: 168 return _CacheInfo(stats[HITS], stats[MISSES], maxsize, 169 len(cache)) 170 finally: 171 lock.release() 172 173 def cache_clear(): 174 """Clear the cache and cache statistics""" 175 lock.acquire() 176 try: 177 cache.clear() 178 root = nonlocal_root[0] 179 root[:] = [root, root, None, None] 180 stats[:] = [0, 0] 181 finally: 182 lock.release() 183 184 wrapper.__wrapped__ = user_function 185 wrapper.cache_info = cache_info 186 wrapper.cache_clear = cache_clear 187 return functools.update_wrapper(wrapper, user_function) 188 189 return decorating_function 190