diff options
| author | Michele Simionato <michele.simionato@gmail.com> | 2018-01-14 07:33:02 +0100 |
|---|---|---|
| committer | Michele Simionato <michele.simionato@gmail.com> | 2018-01-14 07:33:02 +0100 |
| commit | 532347c0d4523ac79025ae1255a0e4ac0b84c3fe (patch) | |
| tree | 911c950fd0bca4b7735ab954723b21ad26e78617 /src | |
| parent | 730113abbee7793749f6b1abca5b73aad5ab1d8d (diff) | |
| download | python-decorator-git-532347c0d4523ac79025ae1255a0e4ac0b84c3fe.tar.gz | |
Added a facility for decorator factories
Diffstat (limited to 'src')
| -rw-r--r-- | src/decorator.py | 28 | ||||
| -rw-r--r-- | src/tests/documentation.py | 80 |
2 files changed, 81 insertions, 27 deletions
diff --git a/src/decorator.py b/src/decorator.py index 788f4bb..15530f0 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -230,13 +230,18 @@ class FunctionMaker(object): return self.make(body, evaldict, addsource, **attrs) -def decorate(func, caller): +def decorate(func, caller, extras=()): """ decorate(func, caller) decorates a function using a caller. """ evaldict = dict(_call_=caller, _func_=func) + es = '' + for i, extra in enumerate(extras): + ex = '_e%d_' % i + evaldict[ex] = extra + es += ex + ', ' fun = FunctionMaker.create( - func, "return _call_(_func_, %(shortsignature)s)", + func, "return _call_(_func_, {}%(shortsignature)s)".format(es), evaldict, __wrapped__=func) if hasattr(func, '__qualname__'): fun.__qualname__ = func.__qualname__ @@ -249,6 +254,7 @@ def decorator(caller, _func=None): # this is obsolete behavior; you should use decorate instead return decorate(_func, caller) # else return a decorator function + defaultargs, defaults = '', () if inspect.isclass(caller): name = caller.__name__.lower() doc = 'decorator(%s) converts functions/generators into ' \ @@ -259,15 +265,23 @@ def decorator(caller, _func=None): else: name = caller.__name__ doc = caller.__doc__ + nargs = caller.__code__.co_argcount + defaultargs = ', '.join(caller.__code__.co_varnames[1:nargs]) + if defaultargs: + defaultargs += ',' + defaults = caller.__defaults__ else: # assume caller is an object with a __call__ method name = caller.__class__.__name__.lower() doc = caller.__call__.__doc__ evaldict = dict(_call=caller, _decorate_=decorate) - return FunctionMaker.create( - '%s(func)' % name, 'return _decorate_(func, _call)', - evaldict, doc=doc, module=caller.__module__, - __wrapped__=caller) - + dec = FunctionMaker.create( + '%s(func, %s)' % (name, defaultargs), + 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' + 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), + evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) + if defaults: + dec.__defaults__ = (None,) + defaults + return dec # ####################### contextmanager ####################### # diff --git a/src/tests/documentation.py b/src/tests/documentation.py index e8fcc55..d77540d 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -41,9 +41,9 @@ What's New in version 4 - **New documentation** There is now a single manual for all Python versions, so I took the - opportunity to overhaul the documentation. So, even if you are - a long-time user, you may want to revisit the docs, since several - examples have been improved. + opportunity to overhaul the documentation and to move it to readthedocs.org. + Even if you are a long-time user, you may want to revisit the docs, since + several examples have been improved. - **Packaging improvements** The code is now also available in wheel format. Integration with @@ -74,6 +74,11 @@ What's New in version 4 defined with the `async def` syntax, and to maintain the `inspect.iscoroutinefunction` check working for the decorated function. +- **Decorator factories** + + From version 4.2 there is facility to define factories of decorators in + a simple way, a feature requested by the users since a long time. + Usefulness of decorators ------------------------------------------------ @@ -358,14 +363,50 @@ Here is an example of usage: >>> func() calling func with args (), {} +The `decorator` function can also be used to define factories of decorators, +i.e. functions returning decorators. In general you can just write something +like this: + +.. code-block:: python + + def decfactory(param1, param2, ...): + def caller(f, *args, **kw): + return somefunc(f, param1, param2, .., *args, **kw) + return decorator(caller) + +This is fully general but requires an additional level of nesting. For this +reasone since version 4.2 there is a facility to build +decorator factories by using a single caller with default arguments i.e. +writing something like this: + +.. code-block:: python + + def caller(f, param1=default1, param2=default2, ..., *args, **kw): + return somefunc(f, param1, param2, *args, **kw) + decfactory = decorator(caller) + +Notice that this simplified approach *only works with default arguments*, +i.e. `param1`, `param2` etc must have known defaults. Thanks to this +restriction, there exists an unique default decorator, i.e. the member +of the family which uses the default values for all parameters. Such +decorator can be written as `decfactory()` with no parameters specified; +moreover, as a shortcut, it is also possible to elide the parenthesis, +a feature much requested by the users. For years I have been opposite +to this feature request, since having expliciti parenthesis to me is more clear +and less magic; however once this feature entered in decorators of +the Python standard library (I am referring to the dataclass decorator +https://www.python.org/dev/peps/pep-0557/) I finally gave up. + +The example below will show how it works in practice. + ``blocking`` ------------------------------------------- Sometimes one has to deal with blocking resources, such as ``stdin``. Sometimes it is better to receive a "busy" message than just blocking everything. -This can be accomplished with a suitable family of decorators, -where the parameter is the busy message: +This can be accomplished with a suitable family of decorators (decorator +factory), parameterize by a string, the busy message: $$blocking @@ -375,7 +416,7 @@ available. For instance: .. code-block:: python - >>> @blocking("Please wait ...") + >>> @blocking(msg="Please wait ...") ... def read_data(): ... time.sleep(3) # simulate a blocking resource ... return "some data" @@ -1431,20 +1472,19 @@ def memoize(f): return decorate(f, _memoize) -def blocking(not_avail): - def _blocking(f, *args, **kw): - if not hasattr(f, "thread"): # no thread running - def set_result(): - f.result = f(*args, **kw) - f.thread = threading.Thread(None, set_result) - f.thread.start() - return not_avail - elif f.thread.isAlive(): - return not_avail - else: # the thread is ended, return the stored result - del f.thread - return f.result - return decorator(_blocking) +@decorator +def blocking(f, msg='blocking', *args, **kw): + if not hasattr(f, "thread"): # no thread running + def set_result(): + f.result = f(*args, **kw) + f.thread = threading.Thread(None, set_result) + f.thread.start() + return msg + elif f.thread.isAlive(): + return msg + else: # the thread is ended, return the stored result + del f.thread + return f.result class User(object): |
