diff options
| author | Michele Simionato <michele.simionato@gmail.com> | 2015-12-09 15:03:11 +0100 |
|---|---|---|
| committer | Michele Simionato <michele.simionato@gmail.com> | 2015-12-09 15:03:11 +0100 |
| commit | e0dc8b2b60cdcdc18695b9fb39810aff6cb62a1b (patch) | |
| tree | 6c3f4d33e34826590abb65c81c5b8aefe75a15ab /src/tests | |
| parent | d261e9c9b0a4e5c7db1a6ad60c3ed2344a6bcbc3 (diff) | |
| parent | a2501ec62a064da277372a840c3b4a268a855583 (diff) | |
| download | python-decorator-git-e0dc8b2b60cdcdc18695b9fb39810aff6cb62a1b.tar.gz | |
Merge pull request #20 from micheles/4.0.5
Release 4.0.5
Diffstat (limited to 'src/tests')
| -rw-r--r-- | src/tests/documentation.py | 47 | ||||
| -rw-r--r-- | src/tests/test.py | 15 |
2 files changed, 57 insertions, 5 deletions
diff --git a/src/tests/documentation.py b/src/tests/documentation.py index 04d62eb..b704b91 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) - <BLANKLINE> ``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') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + 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, +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 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 +``_func_`` you will get a ``NameError``: .. code-block:: python diff --git a/src/tests/test.py b/src/tests/test.py index ab65dfa..5bd34e8 100644 --- a/src/tests/test.py +++ b/src/tests/test.py @@ -77,7 +77,20 @@ class ExtraTestCase(unittest.TestCase): 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.assertNotEqual(f1_orig.__code__.co_filename, + f1.__code__.co_filename) + + def test_no_first_arg(self): + @decorator + def example(*args, **kw): + return args[0](*args[1:], **kw) + + @example + def func(**kw): + return kw + + # there is no confusion when passing args as a keyword argument + self.assertEqual(func(args='a'), {'args': 'a'}) # ################### test dispatch_on ############################# # # adapted from test_functools in Python 3.5 |
