1import copy 2import operator 3from functools import wraps, update_wrapper 4import sys 5 6# You can't trivially replace this `functools.partial` because this binds to 7# classes and returns bound instances, whereas functools.partial (on CPython) 8# is a type and its instances don't bind. 9def curry(_curried_func, *args, **kwargs): 10 def _curried(*moreargs, **morekwargs): 11 return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) 12 return _curried 13 14def memoize(func, cache, num_args): 15 """ 16 Wrap a function so that results for any argument tuple are stored in 17 'cache'. Note that the args to the function must be usable as dictionary 18 keys. 19 20 Only the first num_args are considered when creating the key. 21 """ 22 @wraps(func) 23 def wrapper(*args): 24 mem_args = args[:num_args] 25 if mem_args in cache: 26 return cache[mem_args] 27 result = func(*args) 28 cache[mem_args] = result 29 return result 30 return wrapper 31 32class cached_property: 33 """ 34 Decorator that creates converts a method with a single 35 self argument into a property cached on the instance. 36 """ 37 def __init__(self, func): 38 self.func = func 39 40 def __get__(self, instance, type): 41 res = instance.__dict__[self.func.__name__] = self.func(instance) 42 return res 43 44class Promise: 45 """ 46 This is just a base class for the proxy class created in 47 the closure of the lazy function. It can be used to recognize 48 promises in code. 49 """ 50 pass 51 52def lazy(func, *resultclasses): 53 """ 54 Turns any callable into a lazy evaluated callable. You need to give result 55 classes or types -- at least one is needed so that the automatic forcing of 56 the lazy evaluation code is triggered. Results are not memoized; the 57 function is evaluated on every access. 58 """ 59 60 @total_ordering 61 class __proxy__(Promise): 62 """ 63 Encapsulate a function call and act as a proxy for methods that are 64 called on the result of that function. The function is not evaluated 65 until one of the methods on the result is called. 66 """ 67 __dispatch = None 68 69 def __init__(self, args, kw): 70 self.__args = args 71 self.__kw = kw 72 if self.__dispatch is None: 73 self.__prepare_class__() 74 75 def __reduce__(self): 76 return ( 77 _lazy_proxy_unpickle, 78 (func, self.__args, self.__kw) + resultclasses 79 ) 80 81 def __prepare_class__(cls): 82 cls.__dispatch = {} 83 for resultclass in resultclasses: 84 cls.__dispatch[resultclass] = {} 85 for type_ in reversed(resultclass.mro()): 86 for (k, v) in type_.__dict__.items(): 87 # All __promise__ return the same wrapper method, but they 88 # also do setup, inserting the method into the dispatch 89 # dict. 90 meth = cls.__promise__(resultclass, k, v) 91 if hasattr(cls, k): 92 continue 93 setattr(cls, k, meth) 94 cls._delegate_bytes = bytes in resultclasses 95 cls._delegate_text = str in resultclasses 96 assert not (cls._delegate_bytes and cls._delegate_text), "Cannot call lazy() with both bytes and text return types." 97 if cls._delegate_text: 98 cls.__str__ = cls.__text_cast 99 elif cls._delegate_bytes: 100 cls.__bytes__ = cls.__bytes_cast 101 __prepare_class__ = classmethod(__prepare_class__) 102 103 def __promise__(cls, klass, funcname, method): 104 # Builds a wrapper around some magic method and registers that magic 105 # method for the given type and method name. 106 def __wrapper__(self, *args, **kw): 107 # Automatically triggers the evaluation of a lazy value and 108 # applies the given magic method of the result type. 109 res = func(*self.__args, **self.__kw) 110 for t in type(res).mro(): 111 if t in self.__dispatch: 112 return self.__dispatch[t][funcname](res, *args, **kw) 113 raise TypeError("Lazy object returned unexpected type.") 114 115 if klass not in cls.__dispatch: 116 cls.__dispatch[klass] = {} 117 cls.__dispatch[klass][funcname] = method 118 return __wrapper__ 119 __promise__ = classmethod(__promise__) 120 121 def __text_cast(self): 122 return func(*self.__args, **self.__kw) 123 124 def __bytes_cast(self): 125 return bytes(func(*self.__args, **self.__kw)) 126 127 def __cast(self): 128 if self._delegate_bytes: 129 return self.__bytes_cast() 130 elif self._delegate_text: 131 return self.__text_cast() 132 else: 133 return func(*self.__args, **self.__kw) 134 135 def __eq__(self, other): 136 if isinstance(other, Promise): 137 other = other.__cast() 138 return self.__cast() == other 139 140 def __lt__(self, other): 141 if isinstance(other, Promise): 142 other = other.__cast() 143 return self.__cast() < other 144 145 __hash__ = object.__hash__ 146 147 def __mod__(self, rhs): 148 if self._delegate_text: 149 return str(self) % rhs 150 else: 151 raise AssertionError('__mod__ not supported for non-string types') 152 153 def __deepcopy__(self, memo): 154 # Instances of this class are effectively immutable. It's just a 155 # collection of functions. So we don't need to do anything 156 # complicated for copying. 157 memo[id(self)] = self 158 return self 159 160 @wraps(func) 161 def __wrapper__(*args, **kw): 162 # Creates the proxy object, instead of the actual value. 163 return __proxy__(args, kw) 164 165 return __wrapper__ 166 167def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses): 168 return lazy(func, *resultclasses)(*args, **kwargs) 169 170def allow_lazy(func, *resultclasses): 171 """ 172 A decorator that allows a function to be called with one or more lazy 173 arguments. If none of the args are lazy, the function is evaluated 174 immediately, otherwise a __proxy__ is returned that will evaluate the 175 function when needed. 176 """ 177 @wraps(func) 178 def wrapper(*args, **kwargs): 179 for arg in list(args) + list(kwargs.values()): 180 if isinstance(arg, Promise): 181 break 182 else: 183 return func(*args, **kwargs) 184 return lazy(func, *resultclasses)(*args, **kwargs) 185 return wrapper 186 187empty = object() 188def new_method_proxy(func): 189 def inner(self, *args): 190 if self._wrapped is empty: 191 self._setup() 192 return func(self._wrapped, *args) 193 return inner 194 195class LazyObject: 196 """ 197 A wrapper for another class that can be used to delay instantiation of the 198 wrapped class. 199 200 By subclassing, you have the opportunity to intercept and alter the 201 instantiation. If you don't need to do that, use SimpleLazyObject. 202 """ 203 def __init__(self): 204 self._wrapped = empty 205 206 __getattr__ = new_method_proxy(getattr) 207 208 def __setattr__(self, name, value): 209 if name == "_wrapped": 210 # Assign to __dict__ to avoid infinite __setattr__ loops. 211 self.__dict__["_wrapped"] = value 212 else: 213 if self._wrapped is empty: 214 self._setup() 215 setattr(self._wrapped, name, value) 216 217 def __delattr__(self, name): 218 if name == "_wrapped": 219 raise TypeError("can't delete _wrapped.") 220 if self._wrapped is empty: 221 self._setup() 222 delattr(self._wrapped, name) 223 224 def _setup(self): 225 """ 226 Must be implemented by subclasses to initialise the wrapped object. 227 """ 228 raise NotImplementedError 229 230 # introspection support: 231 __dir__ = new_method_proxy(dir) 232 233 234# Workaround for http://bugs.python.org/issue12370 235_super = super 236 237class SimpleLazyObject(LazyObject): 238 """ 239 A lazy object initialised from any function. 240 241 Designed for compound objects of unknown type. For builtins or objects of 242 known type, use django.utils.functional.lazy. 243 """ 244 def __init__(self, func): 245 """ 246 Pass in a callable that returns the object to be wrapped. 247 248 If copies are made of the resulting SimpleLazyObject, which can happen 249 in various circumstances within Django, then you must ensure that the 250 callable can be safely run more than once and will return the same 251 value. 252 """ 253 self.__dict__['_setupfunc'] = func 254 _super(SimpleLazyObject, self).__init__() 255 256 def _setup(self): 257 self._wrapped = self._setupfunc() 258 259 __bytes__ = new_method_proxy(bytes) 260 __str__ = new_method_proxy(str) 261 262 def __deepcopy__(self, memo): 263 if self._wrapped is empty: 264 # We have to use SimpleLazyObject, not self.__class__, because the 265 # latter is proxied. 266 result = SimpleLazyObject(self._setupfunc) 267 memo[id(self)] = result 268 return result 269 else: 270 return copy.deepcopy(self._wrapped, memo) 271 272 # Because we have messed with __class__ below, we confuse pickle as to what 273 # class we are pickling. It also appears to stop __reduce__ from being 274 # called. So, we define __getstate__ in a way that cooperates with the way 275 # that pickle interprets this class. This fails when the wrapped class is a 276 # builtin, but it is better than nothing. 277 def __getstate__(self): 278 if self._wrapped is empty: 279 self._setup() 280 return self._wrapped.__dict__ 281 282 # Need to pretend to be the wrapped class, for the sake of objects that care 283 # about this (especially in equality tests) 284 __class__ = property(new_method_proxy(operator.attrgetter("__class__"))) 285 __eq__ = new_method_proxy(operator.eq) 286 __hash__ = new_method_proxy(hash) 287 __bool__ = new_method_proxy(bool) # Python 3 288 __nonzero__ = __bool__ # Python 2 289 290 291class lazy_property(property): 292 """ 293 A property that works with subclasses by wrapping the decorated 294 functions of the base class. 295 """ 296 def __new__(cls, fget=None, fset=None, fdel=None, doc=None): 297 if fget is not None: 298 @wraps(fget) 299 def fget(instance, instance_type=None, name=fget.__name__): 300 return getattr(instance, name)() 301 if fset is not None: 302 @wraps(fset) 303 def fset(instance, value, name=fset.__name__): 304 return getattr(instance, name)(value) 305 if fdel is not None: 306 @wraps(fdel) 307 def fdel(instance, name=fdel.__name__): 308 return getattr(instance, name)() 309 return property(fget, fset, fdel, doc) 310 311def partition(predicate, values): 312 """ 313 Splits the values into two sets, based on the return value of the function 314 (True/False). e.g.: 315 316 >>> partition(lambda x: x > 3, range(5)) 317 [0, 1, 2, 3], [4] 318 """ 319 results = ([], []) 320 for item in values: 321 results[predicate(item)].append(item) 322 return results 323 324from functools import total_ordering 325