summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/decorator.py2
-rw-r--r--src/tests/documentation.py47
-rw-r--r--src/tests/test.py14
3 files changed, 56 insertions, 7 deletions
diff --git a/src/decorator.py b/src/decorator.py
index 4e2bb7a..49c1747 100644
--- a/src/decorator.py
+++ b/src/decorator.py
@@ -178,8 +178,6 @@ class FunctionMaker(object):
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>,
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)
- <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')
+ 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
diff --git a/src/tests/test.py b/src/tests/test.py
index ab65dfa..b01bc10 100644
--- a/src/tests/test.py
+++ b/src/tests/test.py
@@ -77,7 +77,19 @@ 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
+
+ self.assertEqual(func(f='a'), {'f': 'a'})
# ################### test dispatch_on ############################# #
# adapted from test_functools in Python 3.5