From a26c55c0d527e1b09b996e29f9d9d816d085addd Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 9 Dec 2015 10:08:23 +0100 Subject: Documented the quirk when decorating functions with keyword arguments --- src/tests/documentation.py | 47 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) (limited to 'src/tests/documentation.py') diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 04d62eb..69e9f4b 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -526,7 +526,6 @@ be added to the generated function: >>> print(f1.__source__) def f1(a, b): f(a, b) - ``FunctionMaker.create`` can take as first argument a string, as in the examples before, or a function. This is the most common @@ -1007,9 +1006,49 @@ callable objects, nor on built-in functions, due to limitations of the ``inspect`` module in the standard library, especially for Python 2.X (in Python 3.5 a lot of such limitations have been removed). -There is a restriction on the names of the arguments: for instance, -if try to call an argument ``_call_`` or ``_func_`` -you will get a ``NameError``: +There is a strange quirk when decorating functions that take keyword +arguments, if one of such arguments has the same name used in the +caller function for the first argument. The quirk was reported by +David Goldstein and here is an example where it is manifest: + +.. code-block: python + + >>> @memoize + ... def getkeys(**kw): + ... return kw.keys() + >>> getkeys(func='a') + Traceback (most recent call last): + ... + TypeError: _memoize() got multiple values for argument 'func' + +The error message looks really strange until you realize that +the caller function `_memoize` uses `func` as first argument, +so there is a confusion between the positional argument and the +keywork arguments. The solution is to change the name of the +first argument in `_memoize`, or to change the implementation as +follows: + +.. code-block: python + + def _memoize(*all_args, **kw): + func = all_args[0] + args = all_args[1:] + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + cache = func.cache # attribute added by memoize + if key not in cache: + cache[key] = func(*args, **kw) + return cache[key] + +We have avoided the need to name the first argument, so the problem +simply disappear. This is a technique that you should keep in mind +when writing decorator for functions with keyword arguments. + +On a similar tone, there is a restriction on the names of the +arguments: for instance, if try to call an argument ``_call_`` or +``_func_`` you will get a ``NameError``: .. code-block:: python -- cgit v1.2.1 From 51f1f8057605df65e39649333139e7d55abfd646 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 9 Dec 2015 10:19:54 +0100 Subject: Added a doctest: +ELLIPSIS to fix old Python versions --- src/tests/documentation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/tests/documentation.py') diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 69e9f4b..81409fc 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -1016,10 +1016,10 @@ David Goldstein and here is an example where it is manifest: >>> @memoize ... def getkeys(**kw): ... return kw.keys() - >>> getkeys(func='a') + >>> getkeys(func='a') # doctest: +ELLIPSIS Traceback (most recent call last): ... - TypeError: _memoize() got multiple values for argument 'func' + TypeError: _memoize() got multiple values for ... 'func' The error message looks really strange until you realize that the caller function `_memoize` uses `func` as first argument, @@ -1043,8 +1043,8 @@ follows: return cache[key] We have avoided the need to name the first argument, so the problem -simply disappear. This is a technique that you should keep in mind -when writing decorator for functions with keyword arguments. +simply disappears. This is a technique that you should keep in mind +when writing decorators for functions with keyword arguments. On a similar tone, there is a restriction on the names of the arguments: for instance, if try to call an argument ``_call_`` or -- cgit v1.2.1 From 0734bb4caf32083152a1571742e07415af54747c Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Wed, 9 Dec 2015 14:58:43 +0100 Subject: Fixed code-block:: --- src/tests/documentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/tests/documentation.py') diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 81409fc..b704b91 100644 --- a/src/tests/documentation.py +++ b/src/tests/documentation.py @@ -1011,7 +1011,7 @@ arguments, if one of such arguments has the same name used in the caller function for the first argument. The quirk was reported by David Goldstein and here is an example where it is manifest: -.. code-block: python +.. code-block:: python >>> @memoize ... def getkeys(**kw): @@ -1028,7 +1028,7 @@ keywork arguments. The solution is to change the name of the first argument in `_memoize`, or to change the implementation as follows: -.. code-block: python +.. code-block:: python def _memoize(*all_args, **kw): func = all_args[0] -- cgit v1.2.1