9 import setuptools
.extern
.more_itertools
11 from typing
import Callable
, TypeVar
14 CallableT
= TypeVar("CallableT", bound
=Callable
[..., object])
19 Compose any number of unary functions into a single unary function.
22 >>> expected = str.strip(textwrap.dedent(compose.__doc__))
23 >>> strip_and_dedent = compose(str.strip, textwrap.dedent)
24 >>> strip_and_dedent(compose.__doc__) == expected
27 Compose also allows the innermost function to take arbitrary arguments.
29 >>> round_three = lambda x: round(x, ndigits=3)
30 >>> f = compose(round_three, int.__truediv__)
31 >>> [f(3*x, x+1) for x in range(1,10)]
32 [1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
35 def compose_two(f1
, f2
):
36 return lambda *args
, **kwargs
: f1(f2(*args
, **kwargs
))
38 return functools
.reduce(compose_two
, funcs
)
41 def method_caller(method_name
, *args
, **kwargs
):
43 Return a function that will call a named method on the
44 target object with optional positional and keyword
47 >>> lower = method_caller('lower')
52 def call_method(target
):
53 func
= getattr(target
, method_name
)
54 return func(*args
, **kwargs
)
61 Decorate func so it's only ever called the first time.
63 This decorator can ensure that an expensive or non-idempotent function
64 will not be expensive on subsequent calls and is idempotent.
66 >>> add_three = once(lambda a: a+3)
74 To reset the stored value, simply clear the property ``saved_result``.
76 >>> del add_three.saved_result
82 Or invoke 'reset()' on it.
91 @functools.wraps(func
)
92 def wrapper(*args
, **kwargs
):
93 if not hasattr(wrapper
, 'saved_result'):
94 wrapper
.saved_result
= func(*args
, **kwargs
)
95 return wrapper
.saved_result
97 wrapper
.reset
= lambda: vars(wrapper
).__delitem
__('saved_result')
103 cache_wrapper
: Callable
[
104 [CallableT
], CallableT
105 ] = functools
.lru_cache(), # type: ignore[assignment]
108 Wrap lru_cache to support storing the cache data in the object instances.
110 Abstracts the common paradigm where the method explicitly saves an
111 underscore-prefixed protected property on first call and returns that
118 ... def method(self, value):
125 >>> for x in range(75):
126 ... res = a.method(x)
130 Note that the apparent behavior will be exactly like that of lru_cache
131 except that the cache is stored on each instance, so values in one
132 instance will not flush values from another, and when an instance is
133 deleted, so are the cached values for that instance.
136 >>> for x in range(35):
137 ... res = b.method(x)
145 Note that if method had been decorated with ``functools.lru_cache()``,
146 a.calls would have been 76 (due to the cached value of 0 having been
147 flushed by the 'b' instance).
149 Clear the cache with ``.cache_clear()``
151 >>> a.method.cache_clear()
153 Same for a method that hasn't yet been called.
156 >>> c.method.cache_clear()
158 Another cache wrapper may be supplied:
160 >>> cache = functools.lru_cache(maxsize=2)
161 >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
166 Caution - do not subsequently wrap the method with another decorator, such
167 as ``@property``, which changes the semantics of the function.
170 http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
171 for another implementation and additional justification.
174 def wrapper(self
: object, *args
: object, **kwargs
: object) -> object:
175 # it's the first call, replace the method with a cached, bound method
176 bound_method
: CallableT
= types
.MethodType( # type: ignore[assignment]
179 cached_method
= cache_wrapper(bound_method
)
180 setattr(self
, method
.__name
__, cached_method
)
181 return cached_method(*args
, **kwargs
)
183 # Support cache clear even before cache has been created.
184 wrapper
.cache_clear
= lambda: None # type: ignore[attr-defined]
186 return ( # type: ignore[return-value]
187 _special_method_cache(method
, cache_wrapper
) or wrapper
191 def _special_method_cache(method
, cache_wrapper
):
193 Because Python treats special methods differently, it's not
194 possible to use instance attributes to implement the cached
197 Instead, install the wrapper method under a different name
198 and return a simple proxy to that wrapper.
200 https://github.com/jaraco/jaraco.functools/issues/5
202 name
= method
.__name
__
203 special_names
= '__getattr__', '__getitem__'
204 if name
not in special_names
:
207 wrapper_name
= '__cached' + name
209 def proxy(self
, *args
, **kwargs
):
210 if wrapper_name
not in vars(self
):
211 bound
= types
.MethodType(method
, self
)
212 cache
= cache_wrapper(bound
)
213 setattr(self
, wrapper_name
, cache
)
215 cache
= getattr(self
, wrapper_name
)
216 return cache(*args
, **kwargs
)
221 def apply(transform
):
223 Decorate a function with a transform function that is
224 invoked on results returned from the decorated function.
227 ... def get_numbers(start):
228 ... "doc for get_numbers"
229 ... return range(start, start+3)
230 >>> list(get_numbers(4))
232 >>> get_numbers.__doc__
233 'doc for get_numbers'
237 return functools
.wraps(func
)(compose(transform
, func
))
242 def result_invoke(action
):
244 Decorate a function with an action function that is
245 invoked on the results returned from the decorated
246 function (for its side-effect), then return the original
249 >>> @result_invoke(print)
250 ... def add_two(a, b):
252 >>> x = add_two(2, 3)
259 @functools.wraps(func
)
260 def wrapper(*args
, **kwargs
):
261 result
= func(*args
, **kwargs
)
270 def invoke(f
, *args
, **kwargs
):
272 Call a function for its side effect after initialization.
274 The benefit of using the decorator instead of simply invoking a function
275 after defining it is that it makes explicit the author's intent for the
276 function to be called immediately. Whereas if one simply calls the
277 function immediately, it's less obvious if that was intentional or
278 incidental. It also avoids repeating the name - the two actions, defining
279 the function and calling it immediately are modeled separately, but linked
280 by the decorator construct.
282 The benefit of having a function construct (opposed to just invoking some
283 behavior inline) is to serve as a scope in which the behavior occurs. It
284 avoids polluting the global namespace with local variables, provides an
285 anchor on which to attach documentation (docstring), keeps the behavior
286 logically separated (instead of conceptually separated or not separated at
287 all), and provides potential to re-use the behavior for testing or other
290 This function is named as a pithy way to communicate, "call this function
291 primarily for its side effect", or "while defining this function, also
292 take it aside and call it". It exists because there's no Python construct
293 for "define and call" (nor should there be, as decorators serve this need
294 just fine). The behavior happens immediately and synchronously.
297 ... def func(): print("called")
302 Use functools.partial to pass parameters to the initial call
304 >>> @functools.partial(invoke, name='bingo')
305 ... def func(name): print("called with", name)
312 def call_aside(*args
, **kwargs
):
314 Deprecated name for invoke.
316 warnings
.warn("call_aside is deprecated, use invoke", DeprecationWarning)
317 return invoke(*args
, **kwargs
)
322 Rate-limit a function (or other callable)
325 def __init__(self
, func
, max_rate
=float('Inf')):
326 if isinstance(func
, Throttler
):
329 self
.max_rate
= max_rate
335 def __call__(self
, *args
, **kwargs
):
337 return self
.func(*args
, **kwargs
)
340 "ensure at least 1/max_rate seconds from last call"
341 elapsed
= time
.time() - self
.last_called
342 must_wait
= 1 / self
.max_rate
- elapsed
343 time
.sleep(max(0, must_wait
))
344 self
.last_called
= time
.time()
346 def __get__(self
, obj
, type=None):
347 return first_invoke(self
._wait
, functools
.partial(self
.func
, obj
))
350 def first_invoke(func1
, func2
):
352 Return a function that when invoked will invoke func1 without
353 any parameters (for its side-effect) and then invoke func2
354 with whatever parameters were passed, returning its result.
357 def wrapper(*args
, **kwargs
):
359 return func2(*args
, **kwargs
)
364 def retry_call(func
, cleanup
=lambda: None, retries
=0, trap
=()):
366 Given a callable func, trap the indicated exceptions
367 for up to 'retries' times, invoking cleanup on the
368 exception. On the final attempt, allow any exceptions
371 attempts
= itertools
.count() if retries
== float('inf') else range(retries
)
372 for attempt
in attempts
:
381 def retry(*r_args
, **r_kwargs
):
383 Decorator wrapper for retry_call. Accepts arguments to retry_call
384 except func and then returns a decorator for the decorated function.
388 >>> @retry(retries=3)
389 ... def my_func(a, b):
390 ... "this is my funk"
397 @functools.wraps(func
)
398 def wrapper(*f_args
, **f_kwargs
):
399 bound
= functools
.partial(func
, *f_args
, **f_kwargs
)
400 return retry_call(bound
, *r_args
, **r_kwargs
)
407 def print_yielded(func
):
409 Convert a generator into a function that prints all yielded elements
413 ... yield 3; yield None
418 print_all
= functools
.partial(map, print)
419 print_results
= compose(more_itertools
.consume
, print_all
, func
)
420 return functools
.wraps(func
)(print_results
)
425 Wrap func so it's not called if its first param is None
427 >>> print_text = pass_none(print)
428 >>> print_text('text')
433 @functools.wraps(func
)
434 def wrapper(param
, *args
, **kwargs
):
435 if param
is not None:
436 return func(param
, *args
, **kwargs
)
441 def assign_params(func
, namespace
):
443 Assign parameters from namespace where func solicits.
445 >>> def func(x, y=3):
447 >>> assigned = assign_params(func, dict(x=2, z=4))
451 The usual errors are raised if a function doesn't receive
452 its required parameters:
454 >>> assigned = assign_params(func, dict(y=3, z=4))
456 Traceback (most recent call last):
457 TypeError: func() ...argument...
459 It even works on methods:
462 ... def meth(self, arg):
464 >>> assign_params(Handler().meth, dict(arg='crystal', foo='clear'))()
467 sig
= inspect
.signature(func
)
468 params
= sig
.parameters
.keys()
469 call_ns
= {k: namespace[k] for k in params if k in namespace}
470 return functools
.partial(func
, **call_ns
)
473 def save_method_args(method
):
475 Wrap a method such that when it is called, the args and kwargs are
479 ... @save_method_args
480 ... def method(self, a, b):
482 >>> my_ob = MyClass()
483 >>> my_ob.method(1, 2)
485 >>> my_ob._saved_method.args
487 >>> my_ob._saved_method.kwargs
489 >>> my_ob.method(a=3, b='foo')
491 >>> my_ob._saved_method.args
493 >>> my_ob._saved_method.kwargs == dict(a=3, b='foo')
496 The arguments are stored on the instance, allowing for
497 different instance to save different args.
499 >>> your_ob = MyClass()
500 >>> your_ob.method({str('x'): 3}, b=[4])
502 >>> your_ob._saved_method.args
504 >>> my_ob._saved_method.args
507 args_and_kwargs
= collections
.namedtuple('args_and_kwargs', 'args kwargs')
509 @functools.wraps(method
)
510 def wrapper(self
, *args
, **kwargs
):
511 attr_name
= '_saved_' + method
.__name
__
512 attr
= args_and_kwargs(args
, kwargs
)
513 setattr(self
, attr_name
, attr
)
514 return method(self
, *args
, **kwargs
)
519 def except_(*exceptions
, replace
=None, use
=None):
521 Replace the indicated exceptions, if raised, with the indicated
522 literal replacement or evaluated expression (if present).
524 >>> safe_int = except_(ValueError)(int)
529 Specify a literal replacement with ``replace``.
531 >>> safe_int_r = except_(ValueError, replace=0)(int)
532 >>> safe_int_r('five')
535 Provide an expression to ``use`` to pass through particular parameters.
537 >>> safe_int_pt = except_(ValueError, use='args[0]')(int)
538 >>> safe_int_pt('five')
544 @functools.wraps(func
)
545 def wrapper(*args
, **kwargs
):
547 return func(*args
, **kwargs
)