Docorator Disected

Ron_Adam radam2 at tampabay.rr.com
Sat Apr 2 19:03:54 EST 2005


On Sat, 02 Apr 2005 21:28:36 GMT, bokr at oz.net (Bengt Richter) wrote:

>I think it might help you to start out with very plain decorators rather than
>decorators as factory functions that return decorator functions that wrap the
>decorated function in a wrapper function. E.g., (this could obviously be
>parameterized as a single decorator factory, but I wanted to show the simplest level
>of decorator functionality)

<clipped interesting examples>  

Thanks for the examples of stacked decorators! :-)

I think I pretty much got it now, I had never needed to pass arguments
to nested "defined" functions before and none of the documentation I
have, ever mentioned that alternative.

So I didn't know I could do this:


def foo(a1):
    def fee(a2):
        return a1+a2
    return fee

fum = foo(2)(6)   <------ !!!

# fum is 8


The interesting thing about this is the 'return fee' statement gets
the (6) apparently appended to it. So it becomes 'return fee(6).

That subtle action is confusing if you don't already know about it,
which I didn't.

In this example.


def foo(a1):
    def fee(a2):
        return a1+a2
    return fee

fum = foo(2)


There is no second set of arguments to append to 'return fee', so the
name fum is pointed to object fee instead and fee is not evaluated.

This second subtle action, is also confusing if you aren't aware of
it. Since the two look the same when you examine the def statements.
So there is no reason to think they would not act the same, both
returning an function object.

Now, add in the @decorator syntax to the mix.  Which hides the extra
argument sets that are passed to the nested defined functions and the
obscuration is complete.  There then is no visual indication of where
the function calls get their arguments from, and this is what I
believe caused me to have so much trouble with this.  

Another inconsistency, although not a bad one, is that nested
'defined' function share scope, but nested function calls do not.  

Now what this means, is it will be very difficult for some people to
put it all together.  I would have gotten it sooner or later, but I
really happy to have help from comp.lang.python. on this one. :)


>I like annotated code walk-throughs. But as others have pointed out,
>it's still a bit buggy ;-)

It helped a lot, but notice that it took me several tries. That's a
strong indicator that decorators are more implicit than explicit and
that goes against the "Explicit is better than Implicit" guideline
that python tries to follow.

Maybe there are ways to make decorators -and- nested function calls a
bit more explicit?

I think a having indicators on the return statements that are meant to
return a value vs object would help readability and take some of the
mystery out as far as the un initiated are concerned.

def foo(a1):
    def fee(a2):
        def fiddle(a3):
            pass
        return a3
    return fee      # Always return a function object.
                    # Error, if argument is passed to it.
        # and

    return fee(a2)  # always require an argument,
                    # error if none is passed to it. 

Or some other way if this breaks something. But it will make it more
apparent what nested function should do.  And give clearer feed back
when trying to use or write decorators.

I'm not sure what might make @decorator more explicit. Maybe allowing
all the function to be specified as an option. Maybe it is already(?)

@decorator(a1)(foo)
def foo():
   pass


So we will have:

def foo(a1):
    def fee(a2):
        def fiddle(a3):
            pass
        return a3
    return fee      # Object always returned here or
                    # or error if argument is received.

@decorator(a1)(fum)  # Last argument optional.
def fum(a3):
    return a3

These I think are small changes that might be acceptable.  

A little more aggressive alterations would be: Requiring the
'function' argument may have a use when using stacked decorators. Then
it could be inserted into a sequence?

@deco3(postcalc)
@deco2(fum)
@deco1(precalc)
def fum(pointxyz):
    return translatepoint(pointxyz)
 
... and that reversed order... (yuck!), is it really necessary?
Readability is important, and it is a big reason people don't jump
ship for some other language. Why the exceptions here?

Ok, don't mean to grip. :-)  I'm sure there's been plenty of that in
past discussions.

Cheers,
Ron




More information about the Python-list mailing list