From e6de7a108ad9837655c33f80723a9adcf07aa69f Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Sun, 15 Mar 2020 11:50:28 +0100 Subject: Initial work for decorator 4.5 --- src/decorator.py | 30 ++++++++++++++---------------- src/tests/documentation.py | 37 ++++++++++--------------------------- src/tests/test.py | 12 ++++++++---- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index a5081e0..ce8644c 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -235,24 +235,22 @@ def decorate(func, caller, extras=()): evaldict[ex] = extra es += ex + ', ' - if '3.5' <= sys.version < '3.6': - # with Python 3.5 isgeneratorfunction returns True for all coroutines - # however we know that it is NOT possible to have a generator - # coroutine in python 3.5: PEP525 was not there yet - generatorcaller = isgeneratorfunction( - caller) and not iscoroutinefunction(caller) + if iscoroutinefunction(caller): + async def fun(*args, **kw): + return await caller(func, *(extras + args), **kw) + elif isgeneratorfunction(caller): + def fun(*args, **kw): + for res in caller(func, *(extras + args), **kw): + yield res else: - generatorcaller = isgeneratorfunction(caller) - if generatorcaller: - fun = FunctionMaker.create( - func, "for res in _call_(_func_, %s%%(shortsignature)s):\n" - " yield res" % es, evaldict, __wrapped__=func) - else: - fun = FunctionMaker.create( - func, "return _call_(_func_, %s%%(shortsignature)s)" % es, - evaldict, __wrapped__=func) - if hasattr(func, '__qualname__'): + def fun(*args, **kw): + return caller(func, *(extras + args), **kw) + fun.__signature__ = inspect.signature(func) + fun.__wrapped__ = func + if hasattr(func, '__qualname__'): # >= Python 3.3 fun.__qualname__ = func.__qualname__ + fun.__annotations__ = func.__annotations__ + fun.__dict__.update(func.__dict__) return fun diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 70f5c3c..c9f6265 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -53,7 +53,7 @@ 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 and to move it to readthedocs.org. + opportunity to overhaul the documentation. Even if you are a long-time user, you may want to revisit the docs, since several examples have been improved. @@ -316,16 +316,17 @@ The decorator works with functions of any signature: ```python >>> @trace -... def f(x, y=1, z=2, *args, **kw): +... def f(x, y=1, *args, **kw): ... pass >>> f(0, 3) -calling f with args (0, 3, 2), {} +calling f with args (0, 3), {} >>> print(getfullargspec(f)) -FullArgSpec(args=['x', 'y', 'z'], varargs='args', varkw='kw', defaults=(1, 2), kwonlyargs=[], kwonlydefaults=None, annotations={}) +FullArgSpec(args=['x', 'y'], varargs='args', varkw='kw', defaults=(1,), kwonlyargs=[], kwonlydefaults=None, annotations={}) ``` + $FUNCTION_ANNOTATIONS ``decorator.decorator`` @@ -1298,20 +1299,11 @@ 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. -On a similar note, there is a restriction on argument names. For instance, -if you name an argument ``_call_`` or ``_func_``, you will get a ``NameError``: - -```python ->>> @trace -... def f(_func_): print(f) -... -Traceback (most recent call last): - ... -NameError: _func_ is overridden in -def f(_func_): - return _call_(_func_, _func_) - -``` +On a similar note, for old versions of the decorator module (< 4.5) +there is a restriction on argument names. For instance, +if you have an argument cakked ``_call_`` or ``_func_``, you will get a +``NameError`` when decorating the function. This restriction has been +lifted in version 4.5. Finally, the implementation is such that the decorated function makes a (shallow) copy of the original function dictionary: @@ -1656,15 +1648,6 @@ def a_test_for_pylons(): if sys.version_info >= (3,): # tests for signatures specific to Python 3 - def test_kwonlydefaults(): - """ - >>> @trace - ... def f(arg, defarg=1, *args, kwonly=2): pass - ... - >>> f.__kwdefaults__ - {'kwonly': 2} - """ - def test_kwonlyargs(): """ >>> @trace diff --git a/src/tests/test.py b/src/tests/test.py index 7ddfaf4..bff23bd 100644 --- a/src/tests/test.py +++ b/src/tests/test.py @@ -125,10 +125,14 @@ class ExtraTestCase(unittest.TestCase): @d1 def f1(x, y, z): pass - self.assertNotEqual(d1.__code__.co_filename, d2.__code__.co_filename) - self.assertNotEqual(f1.__code__.co_filename, f2.__code__.co_filename) - self.assertNotEqual(f1_orig.__code__.co_filename, - f1.__code__.co_filename) + + if sys.version_info < (3, 5): + self.assertNotEqual(d1.__code__.co_filename, + d2.__code__.co_filename) + self.assertNotEqual(f1.__code__.co_filename, + f2.__code__.co_filename) + self.assertNotEqual(f1_orig.__code__.co_filename, + f1.__code__.co_filename) def test_no_first_arg(self): @decorator -- cgit v1.2.1 From ca5bbdd2efa85cc1cc8859efb90a84306055271b Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Mon, 16 Mar 2020 06:43:47 +0100 Subject: Removed exec in decorator --- src/decorator.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index ce8644c..8a4b62d 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -75,6 +75,7 @@ except ImportError: DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') +POS = inspect.Parameter.POSITIONAL_OR_KEYWORD # basic functionality @@ -228,13 +229,6 @@ def decorate(func, caller, extras=()): If the caller is a generator function, the resulting function will be a generator function. """ - evaldict = dict(_call_=caller, _func_=func) - es = '' - for i, extra in enumerate(extras): - ex = '_e%d_' % i - evaldict[ex] = extra - es += ex + ', ' - if iscoroutinefunction(caller): async def fun(*args, **kw): return await caller(func, *(extras + args), **kw) @@ -245,6 +239,7 @@ def decorate(func, caller, extras=()): else: def fun(*args, **kw): return caller(func, *(extras + args), **kw) + fun.__name__ = func.__name__ fun.__signature__ = inspect.signature(func) fun.__wrapped__ = func if hasattr(func, '__qualname__'): # >= Python 3.3 @@ -260,7 +255,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 = '', () + defaultargs = '' if inspect.isclass(caller): name = caller.__name__.lower() doc = 'decorator(%s) converts functions/generators into ' \ @@ -276,18 +271,27 @@ def decorator(caller, _func=None): defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs: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) - 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 + sig = inspect.signature(caller) + dec_params = [p for p in sig.parameters.values() if p.kind is POS] + + def dec(func=None, *args, **kw): + na = len(args) + 1 + extras = args + tuple(kw.get(p.name, p.default) + for p in dec_params[na:]) + if func is None: + return lambda func: decorate(func, caller, extras) + else: + return decorate(func, caller, extras) + dec.__signature__ = sig.replace(parameters=dec_params) + dec.__name__ = name + dec.__doc__ = doc + dec.__wrapped__ = caller + if hasattr(caller, '__qualname__'): # >= Python 3.3 + dec.__qualname__ = caller.__qualname__ + dec.__dict__.update(caller.__dict__) return dec -- cgit v1.2.1 From b5c40c31ce21ce08716ea801f60b2d96eb58d55e Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Mon, 16 Mar 2020 06:45:45 +0100 Subject: Cleanup --- src/decorator.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index 8a4b62d..a6435e9 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -255,25 +255,6 @@ def decorator(caller, _func=None): # this is obsolete behavior; you should use decorate instead return decorate(_func, caller) # else return a decorator function - defaultargs = '' - if inspect.isclass(caller): - name = caller.__name__.lower() - doc = 'decorator(%s) converts functions/generators into ' \ - 'factories of %s objects' % (caller.__name__, caller.__name__) - elif inspect.isfunction(caller): - if caller.__name__ == '': - name = '_lambda_' - else: - name = caller.__name__ - doc = caller.__doc__ - nargs = caller.__code__.co_argcount - ndefs = len(caller.__defaults__ or ()) - defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) - if defaultargs: - defaultargs += ',' - else: # assume caller is an object with a __call__ method - name = caller.__class__.__name__.lower() - doc = caller.__call__.__doc__ sig = inspect.signature(caller) dec_params = [p for p in sig.parameters.values() if p.kind is POS] @@ -286,8 +267,8 @@ def decorator(caller, _func=None): else: return decorate(func, caller, extras) dec.__signature__ = sig.replace(parameters=dec_params) - dec.__name__ = name - dec.__doc__ = doc + dec.__name__ = caller.__name__ + dec.__doc__ = caller.__doc__ dec.__wrapped__ = caller if hasattr(caller, '__qualname__'): # >= Python 3.3 dec.__qualname__ = caller.__qualname__ -- cgit v1.2.1 From 20a43882ca3d3771b8f76ec37c974592d7e299aa Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Sun, 5 Apr 2020 07:38:11 +0200 Subject: Fixed IPython --- src/decorator.py | 4 +++- src/tests/test.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index a6435e9..45d61a4 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -76,6 +76,7 @@ except ImportError: DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') POS = inspect.Parameter.POSITIONAL_OR_KEYWORD +EMPTY = inspect.Parameter.empty # basic functionality @@ -261,7 +262,8 @@ def decorator(caller, _func=None): def dec(func=None, *args, **kw): na = len(args) + 1 extras = args + tuple(kw.get(p.name, p.default) - for p in dec_params[na:]) + for p in dec_params[na:] + if p.default is not EMPTY) if func is None: return lambda func: decorate(func, caller, extras) else: diff --git a/src/tests/test.py b/src/tests/test.py index bff23bd..83e54aa 100644 --- a/src/tests/test.py +++ b/src/tests/test.py @@ -151,7 +151,7 @@ class ExtraTestCase(unittest.TestCase): @decorator def catch_config_error(method, app, *args, **kwargs): return method(app) - catch_config_error(lambda app: None) + catch_config_error(lambda app, **kw: None)(1) def test_add1(self): # similar to what IPython is doing in traitlets.config.application -- cgit v1.2.1 From 2cad696ebb88180c02a7294985f8a81f95e90ff6 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 31 Mar 2021 05:57:58 +0200 Subject: Removed support for old Python versions --- src/decorator.py | 65 ++++++---------------------------------------- src/tests/documentation.py | 2 +- src/tests/test.py | 13 +++++----- 3 files changed, 15 insertions(+), 65 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index 45d61a4..a291557 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -32,47 +32,15 @@ Decorator module, see https://github.com/micheles/decorator/blob/master/docs/documentation.md for the documentation. """ -from __future__ import print_function - import re import sys import inspect import operator import itertools -import collections - -__version__ = '4.4.2' - -if sys.version_info >= (3,): - from inspect import getfullargspec - - def get_init(cls): - return cls.__init__ -else: - FullArgSpec = collections.namedtuple( - 'FullArgSpec', 'args varargs varkw defaults ' - 'kwonlyargs kwonlydefaults annotations') - - def getfullargspec(f): - "A quick and dirty replacement for getfullargspec for Python 2.X" - return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) - - def get_init(cls): - return cls.__init__.__func__ - -try: - iscoroutinefunction = inspect.iscoroutinefunction -except AttributeError: - # let's assume there are no coroutine functions in old Python - def iscoroutinefunction(f): - return False -try: - from inspect import isgeneratorfunction -except ImportError: - # assume no generator function in old Python versions - def isgeneratorfunction(caller): - return False +from contextlib import _GeneratorContextManager +from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction +__version__ = '4.5.0' DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') POS = inspect.Parameter.POSITIONAL_OR_KEYWORD @@ -243,8 +211,7 @@ def decorate(func, caller, extras=()): fun.__name__ = func.__name__ fun.__signature__ = inspect.signature(func) fun.__wrapped__ = func - if hasattr(func, '__qualname__'): # >= Python 3.3 - fun.__qualname__ = func.__qualname__ + fun.__qualname__ = func.__qualname__ fun.__annotations__ = func.__annotations__ fun.__dict__.update(func.__dict__) return fun @@ -272,21 +239,18 @@ def decorator(caller, _func=None): dec.__name__ = caller.__name__ dec.__doc__ = caller.__doc__ dec.__wrapped__ = caller - if hasattr(caller, '__qualname__'): # >= Python 3.3 - dec.__qualname__ = caller.__qualname__ + dec.__qualname__ = caller.__qualname__ dec.__dict__.update(caller.__dict__) return dec # ####################### contextmanager ####################### # -try: # Python >= 3.2 - from contextlib import _GeneratorContextManager -except ImportError: # Python >= 2.5 - from contextlib import GeneratorContextManager as _GeneratorContextManager - class ContextManager(_GeneratorContextManager): + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g, a, k) + def __call__(self, func): """Context manager decorator""" return FunctionMaker.create( @@ -294,19 +258,6 @@ class ContextManager(_GeneratorContextManager): dict(_self_=self, _func_=func), __wrapped__=func) -init = getfullargspec(_GeneratorContextManager.__init__) -n_args = len(init.args) -if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g(*a, **k)) - ContextManager.__init__ = __init__ -elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 - pass -elif n_args == 4: # (self, gen, args, kwds) Python 3.5 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g, a, k) - ContextManager.__init__ = __init__ - _contextmanager = decorator(ContextManager) diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 28369ed..efb42f2 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -1298,7 +1298,7 @@ that. On a similar note, for old versions of the decorator module (< 4.5) there is a restriction on argument names. For instance, -if you have an argument cakked ``_call_`` or ``_func_``, you will get a +if you have an argument called ``_call_`` or ``_func_``, you will get a ``NameError`` when decorating the function. This restriction has been lifted in version 4.5. diff --git a/src/tests/test.py b/src/tests/test.py index 83e54aa..a72b1d7 100644 --- a/src/tests/test.py +++ b/src/tests/test.py @@ -126,13 +126,12 @@ class ExtraTestCase(unittest.TestCase): def f1(x, y, z): pass - if sys.version_info < (3, 5): - self.assertNotEqual(d1.__code__.co_filename, - d2.__code__.co_filename) - self.assertNotEqual(f1.__code__.co_filename, - f2.__code__.co_filename) - self.assertNotEqual(f1_orig.__code__.co_filename, - f1.__code__.co_filename) + self.assertEqual(d1.__code__.co_filename, + d2.__code__.co_filename) + self.assertEqual(f1.__code__.co_filename, + f2.__code__.co_filename) + self.assertEqual(f1_orig.__code__.co_filename, + f1.__code__.co_filename) def test_no_first_arg(self): @decorator -- cgit v1.2.1 From 9b68169b235bebec5eb9d3f4d999ab087e9a7604 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 31 Mar 2021 05:58:22 +0200 Subject: Removed support for old Python versions --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6135835..47dd3cd 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['2.7', '3.5', '3.6', '3.7', '3.8', '3.9'] + python-version: ['3.5', '3.6', '3.7', '3.8', '3.9'] steps: - uses: actions/checkout@v2 -- cgit v1.2.1 From 0a160d0ac0e6ef7a76bdd791a7fc051ce31122cb Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 31 Mar 2021 06:24:26 +0200 Subject: Fixed __kwdefaults__ --- src/decorator.py | 1 + src/tests/documentation.py | 72 +++++++++++++++++++++++++--------------------- src/tests/test.py | 22 ++++---------- 3 files changed, 46 insertions(+), 49 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index a291557..da1c0c4 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -213,6 +213,7 @@ def decorate(func, caller, extras=()): fun.__wrapped__ = func fun.__qualname__ = func.__qualname__ fun.__annotations__ = func.__annotations__ + fun.__kwdefaults__ = func.__kwdefaults__ fun.__dict__.update(func.__dict__) return fun diff --git a/src/tests/documentation.py b/src/tests/documentation.py index efb42f2..6a11b5c 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -6,11 +6,7 @@ import time import functools import itertools import collections -try: - import collections.abc as c -except ImportError: - c = collections - collections.abc = collections +import collections.abc as c from decorator import (decorator, decorate, FunctionMaker, contextmanager, dispatch_on, __version__) @@ -1643,37 +1639,47 @@ def a_test_for_pylons(): """ -if sys.version_info >= (3,): # tests for signatures specific to Python 3 +def test_kwonlydefaults(): + """ + >>> @trace + ... def f(arg, defarg=1, *args, kwonly=2): pass + ... + >>> f.__kwdefaults__ + {'kwonly': 2} + """ - def test_kwonlyargs(): - """ - >>> @trace - ... def func(a, b, *args, y=2, z=3, **kwargs): - ... return y, z - ... - >>> func('a', 'b', 'c', 'd', 'e', y='y', z='z', cat='dog') - calling func with args ('a', 'b', 'c', 'd', 'e'), {'cat': 'dog', 'y': 'y', 'z': 'z'} - ('y', 'z') - """ - def test_kwonly_no_args(): - """# this was broken with decorator 3.3.3 - >>> @trace - ... def f(**kw): pass - ... - >>> f() - calling f with args (), {} - """ +def test_kwonlyargs(): + """ + >>> @trace + ... def func(a, b, *args, y=2, z=3, **kwargs): + ... return y, z + ... + >>> func('a', 'b', 'c', 'd', 'e', y='y', z='z', cat='dog') + calling func with args ('a', 'b', 'c', 'd', 'e'), {'cat': 'dog', 'y': 'y', 'z': 'z'} + ('y', 'z') + """ - def test_kwonly_star_notation(): - """ - >>> @trace - ... def f(*, a=1, **kw): pass - ... - >>> import inspect - >>> inspect.getfullargspec(f) - FullArgSpec(args=[], varargs=None, varkw='kw', defaults=None, kwonlyargs=['a'], kwonlydefaults={'a': 1}, annotations={}) - """ + +def test_kwonly_no_args(): + """# this was broken with decorator 3.3.3 + >>> @trace + ... def f(**kw): pass + ... + >>> f() + calling f with args (), {} + """ + + +def test_kwonly_star_notation(): + """ + >>> @trace + ... def f(*, a=1, **kw): pass + ... + >>> import inspect + >>> inspect.getfullargspec(f) + FullArgSpec(args=[], varargs=None, varkw='kw', defaults=None, kwonlyargs=['a'], kwonlydefaults={'a': 1}, annotations={}) + """ @contextmanager diff --git a/src/tests/test.py b/src/tests/test.py index a72b1d7..83c5649 100644 --- a/src/tests/test.py +++ b/src/tests/test.py @@ -1,21 +1,13 @@ -from __future__ import absolute_import import sys import doctest import unittest import decimal import inspect import functools -import collections -from collections import defaultdict -try: - c = collections.abc -except AttributeError: - c = collections +from asyncio import get_event_loop +from collections import defaultdict, abc as c from decorator import dispatch_on, contextmanager, decorator -try: - from . import documentation as doc -except (ImportError, ValueError, SystemError): # depending on the py-version - import documentation as doc +import documentation as doc @contextmanager @@ -29,22 +21,21 @@ def assertRaises(etype): raise Exception('Expected %s' % etype.__name__) -if sys.version_info >= (3, 5): - exec('''from asyncio import get_event_loop - @decorator async def before_after(coro, *args, **kwargs): return "" + (await coro(*args, **kwargs)) + "" + @decorator def coro_to_func(coro, *args, **kw): return get_event_loop().run_until_complete(coro(*args, **kw)) + class CoroutineTestCase(unittest.TestCase): def test_before_after(self): @before_after async def coro(x): - return x + return x self.assertTrue(inspect.iscoroutinefunction(coro)) out = get_event_loop().run_until_complete(coro('x')) self.assertEqual(out, 'x') @@ -55,7 +46,6 @@ class CoroutineTestCase(unittest.TestCase): return x self.assertFalse(inspect.iscoroutinefunction(coro)) self.assertEqual(coro('x'), 'x') -''') def gen123(): -- cgit v1.2.1 From 1b283da89f8b680c24da724cb5789c0e3f308cdf Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 31 Mar 2021 06:45:50 +0200 Subject: Doc fixes --- CHANGES.md | 3 ++- src/tests/documentation.py | 41 +++++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 89ca8eb..f37ee53 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,8 @@ HISTORY ## unreleased -Ported CI from Travis to GitHub +Dropped support for Python < 3.5 with a substantial simplification of +the code base. Ported CI from Travis to GitHub. ## 4.4.2 (2020-02-29) diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 6a11b5c..b827c1a 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -17,7 +17,7 @@ doc = r"""Decorators for Humans |---|---| |E-mail | michele.simionato@gmail.com| |Version| $VERSION ($DATE)| -|Supports| Python 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8| +|Supports| Python 3.5, 3.6, 3.7, 3.8, 3.9| |Download page| http://pypi.python.org/pypi/decorator/$VERSION| |Installation| ``pip install decorator``| |License | BSD license| @@ -27,22 +27,12 @@ Introduction The ``decorator`` module is over ten years old, but still alive and kicking. It is used by several frameworks (IPython, scipy, authkit, -pylons, pycuda, sugar, ...) and has been stable for a *long* -time. It is your best option if you want to preserve the signature of -decorated functions in a consistent way across Python -releases. Version 4 is fully compatible with the past, except for -one thing: support for Python 2.4 and 2.5 has been dropped. That -decision made it possible to use a single code base both for Python -2.X and Python 3.X. This is a *huge* bonus, since I could remove over -2,000 lines of duplicated documentation/doctests. Having to maintain -separate docs for Python 2 and Python 3 effectively stopped any -development on the module for several years. Moreover, it is now -trivial to distribute the module as an universal - [wheel](http://pythonwheels.com) since 2to3 is no more -required. Since Python 2.5 has been released ages ago (in 2006), I felt that -it was reasonable to drop the support for it. If you need to support -ancient versions of Python, stick with the decorator module version -3.4.2. The current version supports all Python releases from 2.6 up. +pylons, pycuda, sugar, ...) and has been stable for a *long* time. It +is your best option if you want to preserve the signature of decorated +functions in a consistent way across Python releases. Version 5.X +requires Python versions greater than 3.4, but you can support back to +Python 2.6 by using version 4.X and version 3.X is able to support +even Python 2.4 and 2.5. What's New in version 4 ----------------------- @@ -1292,11 +1282,18 @@ 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. -On a similar note, for old versions of the decorator module (< 4.5) -there is a restriction on argument names. For instance, -if you have an argument called ``_call_`` or ``_func_``, you will get a -``NameError`` when decorating the function. This restriction has been -lifted in version 4.5. +On a similar note, there is a restriction on argument names. For instance, +if you name an argument ``_call_`` or ``_func_``, you will get a ``NameError``: + +```python +>>> @trace +... def f(_func_): print(f) +... +Traceback (most recent call last): + ... +NameError: _func_ is overridden in +def f(_func_): + return _call_(_func_, _func_) Finally, the implementation is such that the decorated function makes a (shallow) copy of the original function dictionary: -- cgit v1.2.1 From 95132174ed34089ec620e928597fb8f4d2dd0e92 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 31 Mar 2021 06:49:33 +0200 Subject: Added dec.__kwdefaults__ --- src/decorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/decorator.py b/src/decorator.py index da1c0c4..4aa312b 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -241,6 +241,7 @@ def decorator(caller, _func=None): dec.__doc__ = caller.__doc__ dec.__wrapped__ = caller dec.__qualname__ = caller.__qualname__ + dec.__kwdefaults__ = getattr(caller, '__kwdefaults__', None) dec.__dict__.update(caller.__dict__) return dec -- cgit v1.2.1 From 3bbdb6dd1fb04c8b8379c751e5e0e0afe04c1d3f Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 31 Mar 2021 07:12:08 +0200 Subject: Not using the FunctionMaker in ContextManager --- src/decorator.py | 22 +++++++++++++--------- src/tests/documentation.py | 13 ------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/decorator.py b/src/decorator.py index 4aa312b..2293c6e 100644 --- a/src/decorator.py +++ b/src/decorator.py @@ -1,6 +1,6 @@ # ######################### LICENSE ############################ # -# Copyright (c) 2005-2020, Michele Simionato +# Copyright (c) 2005-2021, Michele Simionato # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -47,7 +47,7 @@ POS = inspect.Parameter.POSITIONAL_OR_KEYWORD EMPTY = inspect.Parameter.empty -# basic functionality +# this is not used anymore in the core, but kept for backward compatibility class FunctionMaker(object): """ An object with the ability to create functions with a given signature. @@ -114,7 +114,9 @@ class FunctionMaker(object): raise TypeError('You are decorating a non function: %s' % func) def update(self, func, **kw): - "Update the signature of func with the data in self" + """ + Update the signature of func with the data in self + """ func.__name__ = self.name func.__doc__ = getattr(self, 'doc', None) func.__dict__ = getattr(self, 'dict', {}) @@ -131,7 +133,9 @@ class FunctionMaker(object): func.__dict__.update(kw) def make(self, src_templ, evaldict=None, addsource=False, **attrs): - "Make a new function from a given template and update the signature" + """ + Make a new function from a given template and update the signature + """ src = src_templ % vars(self) # expand name and signature evaldict = evaldict or {} mo = DEF.search(src) @@ -219,7 +223,7 @@ def decorate(func, caller, extras=()): def decorator(caller, _func=None): - """decorator(caller) converts a caller function into a decorator""" + "decorator(caller) converts a caller function into a decorator" if _func is not None: # return a decorated function # this is obsolete behavior; you should use decorate instead return decorate(_func, caller) @@ -254,10 +258,10 @@ class ContextManager(_GeneratorContextManager): return _GeneratorContextManager.__init__(self, g, a, k) def __call__(self, func): - """Context manager decorator""" - return FunctionMaker.create( - func, "with _self_: return _func_(%(shortsignature)s)", - dict(_self_=self, _func_=func), __wrapped__=func) + def caller(f, *a, **k): + with self: + return f(*a, **k) + return decorate(func, caller) _contextmanager = decorator(ContextManager) diff --git a/src/tests/documentation.py b/src/tests/documentation.py index b827c1a..ec04c6d 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -1282,19 +1282,6 @@ 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. -On a similar note, there is a restriction on argument names. For instance, -if you name an argument ``_call_`` or ``_func_``, you will get a ``NameError``: - -```python ->>> @trace -... def f(_func_): print(f) -... -Traceback (most recent call last): - ... -NameError: _func_ is overridden in -def f(_func_): - return _call_(_func_, _func_) - Finally, the implementation is such that the decorated function makes a (shallow) copy of the original function dictionary: -- cgit v1.2.1