self-promotion of the decorator module (Was: How to learn OO of python?)

M

Michele Simionato

could said:
I think decorator is a function which return a function, is this right?
e.g. The decorator below if from http://www.python.org/peps/pep-0318.html#id1.

def accepts(*types):
def check_accepts(f):
assert len(types) == f.func_code.co_argcount
def new_f(*args, **kwds):
for (a, t) in zip(args, types):
assert isinstance(a, t), \
"arg %r does not match %s" % (a,t)
return f(*args, **kwds)
new_f.func_name = f.func_name
return new_f
return check_accepts

After I saw all the examples, I concluded that every decorator must
define an inner function which takes only one argument, the
function to decorate. Is this right?

Yes, but you may want to look at my decorator module:

http://www.phyast.pitt.edu/~micheles/python/decorator.zip

Your example would be written as follows:

from decorator import decorator

def accepts(*types):
def check_accepts(f, *args, **kw):
assert len(types) == f.func_code.co_argcount
for a, t in zip(args, types):
assert isinstance(a, t), "arg %r does not match %s" % (a,t)
return f(*args, **kw)
return decorator(check_accepts)

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).


Michele Simionato
 
D

Duncan Booth

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.
 
M

Michele Simionato

Notice that the decorator module is at version 0.4 and I still consider
it as experimental. The reason why I posted the link here is to get
feedback and it seems I got it ;)

I will upgrade to version 0.5 shortly, fixing the easily fixable bugs
and
documenting what cannot be easily fixed.

Thanks for testing border cases I would have never imagined!

Michele Simionato
 
M

Michele Simionato

Duncan Booth:
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.

It does not look too simple either. Whereas I can easily
build valid Python identifiers which are extremely unlikely
to clash with any identifier I can have in my program, it
seems impossible to get an identifier which cannot be
shadowed later . What about renaming
name-> _name_, call -> _call_, func-> _func_, defarg->_defarg_ and
checking that the argument list
does not contain such reserved names by means of an assert statement?

Michele Simionato
 
M

Michele Simionato

I have uploaded version 0.5 that should fix all the
subtle bugs you pointed out, but there could be others
(I would not be surprised in that case ;).
I will add a test suite for various corner cases tomorrow.

BTW, I cannot decide if exotic function signatures are
a feature or an accident ...


Michele Simionato
 
D

Duncan Booth

Michele said:
Duncan Booth:

It does not look too simple either. Whereas I can easily
build valid Python identifiers which are extremely unlikely
to clash with any identifier I can have in my program, it
seems impossible to get an identifier which cannot be
shadowed later . What about renaming
name-> _name_, call -> _call_, func-> _func_, defarg->_defarg_ and
checking that the argument list
does not contain such reserved names by means of an assert statement?

I'm not sure I see the problem. You only have to pick names which are not
the name of the current function not any of its arguments. These names get
substituted into the string used to define the function which is exec'ed in
its own private namespace. So long as they are unique within that namespace
there shouldn't be a problem.
 
M

Michele Simionato

Yep, I was wondering about irrelevant things, there is no problem in
this case,
actually.

Michele Simionato
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
SterlingLa
Top