diff options
| author | Michele Simionato <michele.simionato@gmail.com> | 2021-04-03 09:54:59 +0200 |
|---|---|---|
| committer | Michele Simionato <michele.simionato@gmail.com> | 2021-04-03 09:54:59 +0200 |
| commit | e6e3855c47e3b86054b4640a828b66a837134fc5 (patch) | |
| tree | f39d89b13645d4d28d835ec4abb36d57e6bf09b0 /src | |
| parent | 068743727d9b2f0e9ffb39cc4ebcdf5331a56861 (diff) | |
| download | python-decorator-git-e6e3855c47e3b86054b4640a828b66a837134fc5.tar.gz | |
Documented incompatibility with previous versions
Diffstat (limited to 'src')
| -rw-r--r-- | src/decorator.py | 14 | ||||
| -rw-r--r-- | src/tests/documentation.py | 125 |
2 files changed, 101 insertions, 38 deletions
diff --git a/src/decorator.py b/src/decorator.py index 1fbd1ec..465558f 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -37,6 +37,7 @@ import sys import inspect import operator import itertools +import abc from contextlib import _GeneratorContextManager from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction @@ -222,6 +223,19 @@ def decorate(func, caller, extras=()): return fun +class Decorator(metaclass=abc.ABCMeta): + """ + Abstract base class. Subclass it and override the caller method to + define your own decorator factories. + """ + @abc.abstractmethod + def caller(self, func, *args, **kw): + pass + + def __call__(self, func): + return decorate(func, self.caller) + + def decorator(caller, _func=None): """ decorator(caller) converts a caller function into a decorator diff --git a/src/tests/documentation.py b/src/tests/documentation.py index aa6f3db..e34040b 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -44,6 +44,8 @@ signature of a decorated function without resorting to "exec" tricks). The simplification gives a very neat advantage: in case of exceptions raised in decorated functions the traceback is nicer than it used to be. That counts as a new feature in my book ;-) +There is also a change of logic that breaks some decorators, see the section +about caveats and limitations. What's New in version 4 ----------------------- @@ -1103,50 +1105,41 @@ not use any cache, whereas the ``singledispatch`` implementation does. Caveats and limitations ------------------------------------------- -It should be noted that in Python 3.5, a *lot* of improvements have -been made: you can decorate a function with -``func_tools.update_wrapper``, and ``pydoc`` will see the correct -signature. Unfortunately, the function will still have an incorrect -signature internally, as you can see by using -``inspect.getfullargspec``; so, all documentation tools using -``inspect.getfullargspec`` - which has been rightly deprecated - -will see the wrong signature. +Version 5.X breaks compatibility with the past, by making decorators +more similar to the ones that can be defined with ``functools.wraps``. +An example will make the issue clear: -One thing you should be aware of, is the performance penalty of decorators. -The worse case is shown by the following example: +$$chatty -```bash - $ cat performance.sh - python3 -m timeit -s " - from decorator import decorator +$$printsum - @decorator - def do_nothing(func, *args, **kw): - return func(*args, **kw) +In this example ``x`` and ``y`` are positional arguments with defaults. +In previous versions of the decorator module +(< 5) a call to ``printsum()`` would have passed ``args==(1, 2)`` to +the caller, with an empty ``kwargs`` dictionary. In version 5.X instead +even ``args`` is empty: - @do_nothing - def f(): - pass - " "f()" +>>> printsum() +() {} +3 - python3 -m timeit -s " - def f(): - pass - " "f()" +``args`` become non-empty only if you pass the arguments as positional -``` -On my laptop, using the ``do_nothing`` decorator instead of the -plain function is five times slower: +>>> printsum(1) +(1,) {} +3 -```bash - $ bash performance.sh - 1000000 loops, best of 3: 1.39 usec per loop - 1000000 loops, best of 3: 0.278 usec per loop -``` -Of course, a real life function probably does something more useful -than the function ``f`` here, so the real life performance penalty -*could* be negligible. As always, the only way to know if there is a -penalty in your specific use case is to measure it. +and not if you pass them as keyword arguments: + +>>> printsum(x=1) +() {'x': 1} +3 + +This can be pretty confusing since non-keyword arguments are passed as +keywork arguments, but it the way it works with ``functools.wraps`` and +the way many people expect it to work: + +$$chattywrapper In the present implementation, decorators generated by ``decorator`` can only be used on user-defined Python functions, methods or coroutines. @@ -1205,7 +1198,7 @@ notice that lately I have come to believe that decorating functions with keyword arguments is not such a good idea, and you may want not to do that. -Finally, the implementation is such that the decorated function makes +The implementation is such that the decorated function makes a (shallow) copy of the original function dictionary: ```python @@ -1223,6 +1216,43 @@ a (shallow) copy of the original function dictionary: ``` +Finally, you should be aware of the performance penalty of decorators. +The worse case is shown by the following example: + +```bash + $ cat performance.sh + python3 -m timeit -s " + from decorator import decorator + + @decorator + def do_nothing(func, *args, **kw): + return func(*args, **kw) + + @do_nothing + def f(): + pass + " "f()" + + python3 -m timeit -s " + def f(): + pass + " "f()" + +``` +On my laptop, using the ``do_nothing`` decorator instead of the +plain function is five times slower: + +```bash + $ bash performance.sh + 1000000 loops, best of 3: 1.39 usec per loop + 1000000 loops, best of 3: 0.278 usec per loop +``` + +Of course, a real life function probably does something more useful +than the function ``f`` here, so the real life performance penalty +*could* be negligible. As always, the only way to know if there is a +penalty in your specific use case is to measure it. + LICENSE (2-clause BSD) --------------------------------------------- @@ -1760,6 +1790,25 @@ def operation2(): """ time.sleep(.1) + +@decorator +def chatty(func, *args, **kwargs): + print(args, kwargs) + return func(*args, **kwargs) + + +@chatty +def printsum(x=1, y=2): + print(x + y) + + +def chattywrapper(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + print(args, kwargs) + return func(*args, **kwargs) + return functools.wraps(wrapper) + # ####################### changing the signature ########################## # |
