summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichele Simionato <michele.simionato@gmail.com>2021-04-03 09:54:59 +0200
committerMichele Simionato <michele.simionato@gmail.com>2021-04-03 09:54:59 +0200
commite6e3855c47e3b86054b4640a828b66a837134fc5 (patch)
treef39d89b13645d4d28d835ec4abb36d57e6bf09b0
parent068743727d9b2f0e9ffb39cc4ebcdf5331a56861 (diff)
downloadpython-decorator-git-e6e3855c47e3b86054b4640a828b66a837134fc5.tar.gz
Documented incompatibility with previous versions
-rw-r--r--docs/documentation.md125
-rw-r--r--src/decorator.py14
-rw-r--r--src/tests/documentation.py125
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 ########################## #