self-promotion of the decorator module

Duncan Booth duncan.booth at invalid.invalid
Thu May 19 04:46:21 EDT 2005


Michele Simionato wrote:

> 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:

>>> from decorator import *
>>> @decorator
def chatty(f, *args, **kw):
	print "Calling %r" % f.__name__
	return f(*args, **kw)

>>> @chatty
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:

>>> class Default:
	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:

>>> @chatty
def f(): pass

>>> f(3)

Traceback (most recent call last):
  File "<pyshell#15>", line 1, in -toplevel-
    f(3)
TypeError: <lambda>() takes no arguments (1 given)
>>> 

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.



More information about the Python-list mailing list