decorators as generalized pre-binding hooks

Bengt Richter bokr at oz.net
Sat Jul 9 17:06:13 EDT 2005


;-)
We have

    @deco
    def foo(): pass
as sugar (unless there's an uncaught exception in the decorator) for 
    def foo(): pass
    foo = deco(foo)

The binding of a class name is similar, and class decorators would seem natural, i.e.,

    @cdeco
    class Foo: pass
for
    class Foo: pass
    Foo = cdeco(Foo)

What is happening is we are intercepting the binding of some object
and letting the decorator do something to the object before the binding occurs.

So why not

    @deco
    foo = lambda:pass
equivalent to
    foo = deco(lambda:pass)

and from there,
    @deco
    <left-hand-side> = <right-hand-side>
being equivalent to
    <left-hand-side> = deco(<right-hand-side>)

e.g.,
    @range_check(1,5)
    a = 42
for
    a = range_check(1,5)(42)

or
    @default_value(42) 
    b = c.e['f']('g')
for
    b = default_value(42)(c.e['f']('g'))

Hm, binding-intercept-decoration could be sugar for catching exceptions too,
and passing them to the decorator, e.g., the above could be sugar for

    try:
        b = default_value(42)(c.e['f']('g'))
    except Exception, e:
        b = default_value(__exception__=e) # so decorator can check
                               # and either return a value or just re-raise with raise [Note 1]

This might be useful for plain old function decorators too, if you wanted the decorator
to define the policy for substituting something if e.g. a default argument evaluation
throws and exception. Thus

    @deco
    def foo(x=a/b): pass # e.g., what if b==0?
as
    try:
        def foo(x=a/b): pass # e.g., what if b==0?
        foo = deco(foo)
    except Exception, e:
        if not deco.func_code.co_flags&0x08: raise #avoid mysterious unexpected keyword TypeError
        foo = deco(__exception__=e)

[Note 1:]
Interestingly raise doesn't seem to have to be in the same frame or down-stack, so a decorator
called as above could re-raise:

 >>> def deco(**kw):
 ...     print kw
 ...     raise
 ...
 >>> try: 1/0
 ... except Exception, e: deco(__exception__=e)
 ...
 {'__exception__': <exceptions.ZeroDivisionError instance at 0x02EF190C>}
 Traceback (most recent call last):
   File "<stdin>", line 2, in ?
   File "<stdin>", line 1, in ?
 ZeroDivisionError: integer division or modulo by zero


orthogonal-musing-ly ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list