summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichele Simionato <michele.simionato@gmail.com>2018-01-14 07:33:02 +0100
committerMichele Simionato <michele.simionato@gmail.com>2018-01-14 07:33:02 +0100
commit532347c0d4523ac79025ae1255a0e4ac0b84c3fe (patch)
tree911c950fd0bca4b7735ab954723b21ad26e78617 /src
parent730113abbee7793749f6b1abca5b73aad5ab1d8d (diff)
downloadpython-decorator-git-532347c0d4523ac79025ae1255a0e4ac0b84c3fe.tar.gz
Added a facility for decorator factories
Diffstat (limited to 'src')
-rw-r--r--src/decorator.py28
-rw-r--r--src/tests/documentation.py80
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):