Docorator Disected

Bengt Richter bokr at oz.net
Sat Apr 2 16:28:36 EST 2005


On Sat, 02 Apr 2005 14:29:08 GMT, Ron_Adam <radam2 at tampabay.rr.com> wrote:

>
>I was having some difficulty figuring out just what was going on with
>decorators.  So after a considerable amount of experimenting I was
>able to take one apart in a way.  It required me to take a closer look
>at function def's and call's, which is something I tend to take for
>granted.
>
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)

 >>> def decoa(f):
 ...     f.decstr = getattr(f, 'decstr', '') + 'a'
 ...     return f
 ...
 >>> def decob(f):
 ...     f.decstr = getattr(f, 'decstr', '') + 'b'
 ...     return f
 ...
 >>> def decoc(f):
 ...     f.decstr = getattr(f, 'decstr', '') + 'c'
 ...     return f
 ...
 >>> @decoa
 ... @decoc
 ... @decob
 ... @decob
 ... def foo(): pass
 ...
 >>> foo.decstr
 'bbca'

I.e.,

    @decoa
    @decoc
    def foo(): pass

is almost[1] exactly equal to (note calling order c then a)

    def foo(): pass
    foo = decoc(foo)
    foo = decoa(foo)

[1] One difference is that foo = deco(foo) is a RE-binding of foo,
    and the binding wouldn't happen at all if the @deco version
    raised an exception in deco. E.g.,

 >>> def deco(f): raise NotImplementedError
 ...

foo not yet defined:
 >>> foo
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 NameError: name 'foo' is not defined

Try the bad decorator:
 >>> @deco
 ... def foo(): pass
 ...
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 1, in deco
 NotImplementedError

No go, and foo still undefined:
 >>> foo
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 NameError: name 'foo' is not defined

But the other way around:

bar undefined to start:
 >>> bar
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 NameError: name 'bar' is not defined

Define it sucessfully:
 >>> def bar():pass
 ...
 >>> bar
 <function bar at 0x02EE8B54>

Try to decorate the old-fashioned way:
 >>> bar = deco(bar)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 1, in deco
 NotImplementedError

 >>> bar
 <function bar at 0x02EE8B54>

Still there, defined as before (well, strictly speaking, not
necessarily as before: with bar already defined, deco could
have messed with the existing bar and THEN raised the exception).
Which would also happen with @deco, just that the new binding to bar
wouln't happen.

 >>> def decobomb(f):
 ...     f.bomb = 'bombed'
 ...     raise Exception, 'Did it bomb the existing function?'
 ...
 >>> def foo(): pass
 ...
 >>> vars(foo).keys()
 []
 >>> @decobomb
 ... def foo(): pass
 ...
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 3, in decobomb
 Exception: Did it bomb the existing function?
 >>> vars(foo).keys()
 []

Nope, seems that was a new foo that got bombed and then not
bound to replace the old foo.

 >>> foo.bomb
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AttributeError: 'function' object has no attribute 'bomb'


>I'm not sure this is 100%, or if there are other ways to view it, but
>it seems to make sense when viewed this way.
I like annotated code walk-throughs. But as others have pointed out,
it's still a bit buggy ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list