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 | |
parent | 068743727d9b2f0e9ffb39cc4ebcdf5331a56861 (diff) | |
download | python-decorator-git-e6e3855c47e3b86054b4640a828b66a837134fc5.tar.gz |
Documented incompatibility with previous versions
-rw-r--r-- | docs/documentation.md | 125 | ||||
-rw-r--r-- | src/decorator.py | 14 | ||||
-rw-r--r-- | src/tests/documentation.py | 125 |
3 files changed, 188 insertions, 76 deletions
diff --git a/docs/documentation.md b/docs/documentation.md index 95a0903..ecd7a79 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -4,7 +4,7 @@ Decorators for Humans |Author | Michele Simionato| |---|---| |E-mail | michele.simionato@gmail.com| -|Version| 5.0.3 (2021-04-02)| +|Version| 5.0.3 (2021-04-03)| |Supports| Python 3.5, 3.6, 3.7, 3.8, 3.9| |Download page| http://pypi.python.org/pypi/decorator/5.0.3| |Installation| ``pip install decorator``| @@ -33,6 +33,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 ----------------------- @@ -1454,50 +1456,60 @@ 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: - -```bash - $ cat performance.sh - python3 -m timeit -s " - from decorator import decorator +```python @decorator - def do_nothing(func, *args, **kw): - return func(*args, **kw) - - @do_nothing - def f(): - pass - " "f()" + def chatty(func, *args, **kwargs): + print(args, kwargs) + return func(*args, **kwargs) +``` - python3 -m timeit -s " - def f(): - pass - " "f()" +```python + @chatty + def printsum(x=1, y=2): + print(x + y) ``` -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 +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: + +>>> printsum() +() {} +3 + +``args`` become non-empty only if you pass the arguments as positional + +>>> printsum(1) +(1,) {} +3 + +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: + +```python + + def chattywrapper(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + print(args, kwargs) + return func(*args, **kwargs) + return functools.wraps(wrapper) ``` -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. In the present implementation, decorators generated by ``decorator`` can only be used on user-defined Python functions, methods or coroutines. @@ -1556,7 +1568,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 @@ -1574,6 +1586,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) --------------------------------------------- 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 ########################## # |