Michele said:
with the following advantages:
1. one-level of nesting is saved ("flat is better than nested")
2. name, docstring and dictionary of the original function are
preserved;
3. the signature of the original function is preserved (this one is
nontrivial).
And at least the following bugs:
def chatty(f, *args, **kw):
print "Calling %r" % f.__name__
return f(*args, **kw)
def f((x,y)): pass
Traceback (most recent call last):
File "<pyshell#11>", line 1, in -toplevel-
@chatty
File "C:\Work\decorator\decorator.py", line 119, in __call__
return _decorate(func, self.caller)
File "C:\Work\decorator\decorator.py", line 89, in _decorate
dec_func = eval(lambda_src, evaldict)
File "<string>", line 1
lambda ['x', 'y']: call(func, ['x', 'y'])
^
SyntaxError: invalid syntax
I would have thought the code would be simpler if it used
inspect.formatargspec to produce an argument list which is correctly
formatted for all the weird corner cases:
def __init__(self, n):
self.n = n
def __repr__(self):
return 'defarg[%d]' % self.n
def f(x, y=1, z=2, *args, **kw): pass
a,v,vk,d = inspect.getargspec(f)
inspect.formatargspec(a,v,vk,map(Default, range(len(d)))) '(x, y=defarg[0], z=defarg[1], *args, **kw)'
def g(x, (y,z)=(1,2), *args, **kw): pass
a,v,vk,d = inspect.getargspec(g)
inspect.formatargspec(a,v,vk,map(Default, range(len(d)))) '(x, (y, z)=defarg[0], *args, **kw)'
d ((1, 2),)
Even if we manage to decorate the function, the decorated object has the
wrong name in tracebacks:
def f(): pass
Traceback (most recent call last):
File "<pyshell#15>", line 1, in -toplevel-
f(3)
Using a lambda function here seems needlessly perverse. You know the name
of the function you want to define and you are going to eval a string, so
why not exec the string instead
"def %(name)s %(fullsign)s: call(func, %(shortsign)s)" % getinfo(func)
and then just pick the function out of the namespace after the exec?
e.g.
def _decorate(func, caller):
"""Takes a function and a caller and returns the function
decorated with that caller. The decorated function is obtained
by evaluating a lambda function with the correct signature.
"""
lambda_src = "def %(name)s(%(fullsign)s): call(func, %(shortsign)s)" %
getinfo(func)
# print lambda_src # for debugging
execdict = dict(func=func, call=caller, defarg=func.func_defaults or
())
exec lambda_src in execdict
dec_func = execdict.get(func.__name__)
dec_func.__doc__ = func.__doc__
dec_func.__dict__ = func.__dict__
return dec_func
There is still a problem with this one though. If the function has
arguments named 'call', 'func', or 'defarg' (or for that matter my
code above introduces a new problem if the function is called by one
of those names) nasty things will happen. Fortunately you have a list of
argument names readily available so it shouldn't be too hard to generate
unique names to use in their place.