Skip to content

Commit

Permalink
Documented the quirk when decorating functions with keyword arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
micheles committed Dec 9, 2015
1 parent 1ed48fd commit a26c55c
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 7 deletions.
Binary file modified documentation.pdf
Binary file not shown.
2 changes: 0 additions & 2 deletions src/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,6 @@ def make(self, src_templ, evaldict=None, addsource=False, **attrs):
for n in names:
if n in ('_func_', '_call_'):
raise NameError('%s is overridden in\n%s' % (n, src))
if not src.endswith('\n'): # add a newline just for safety
src += '\n' # this is needed in old versions of Python

# Ensure each generated function has a unique filename for profilers
# (such as cProfile) that depend on the tuple of (<filename>,
Expand Down
47 changes: 43 additions & 4 deletions src/tests/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,6 @@
>>> 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
Expand Down Expand Up @@ -1007,9 +1006,49 @@ def f():
``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
Expand Down
14 changes: 13 additions & 1 deletion src/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,19 @@ def f1(x, y, z):

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

self.assertEqual(func(f='a'), {'f': 'a'})

# ################### test dispatch_on ############################# #
# adapted from test_functools in Python 3.5
Expand Down

0 comments on commit a26c55c

Please sign in to comment.