Docorator Disected

Kay Schluehr kay.schluehr at gmx.net
Sat Apr 2 11:39:35 EST 2005


Ron_Adam wrote:

> def decorator(d_arg):     # (7) Get 'Goodbye' off stack
>
>     def get_function(function): # (8) Get func object off stack
>
>         def wrapper(f_arg):        # (9) Get 'Hello' off stack
>
>             new_arg = f_arg+'-'+d_arg
>             result = function(new_arg)  # (10) Put new_arg on stack
>                                         # (11) Call func object
>
>             return result          # (14) Return result to wrapper
>
>         return wrapper        # (15) Return result to get_function
>
>     return get_function    # (16) Return result to caller of func
>
> @decorator('Goodbye')   # (5) Put 'Goodbye' on stack
>                         # (6) Do decorator
>
> def func(s):            # (12) Get new_arg off stack
>
>     return s            # (13) Return s to result

There is actually nothing mysterious about decorators. It is nothing
more than ordinary function composition, executed when the decorated
function is defined. In case of Your definition it, the composition
rules are:

decorator("Goodbye")(func)(s) = get_function(func)(s) = wrapper(s),
where wrapper stores "Goodbye" in the local d_arg.

Or a bit more formally we state the composition principle:

Args x Func -> Func, where decorator() is a function of Args, that
returns a function Func -> Func. As Guido had shown recently in his
Artima blog, Func need not be an instance of an ordinary function but
can be a function-object like his MultiMethod :

http://www.artima.com/weblogs/viewpost.jsp?thread=101605

It is also possible to extend this view by "chaining" decorators.

decorator : Args(2) x (Args(1) x Func - > Func ) -> Func.

To understand decorator chains it is very helpfull to accept the
functional view instead of arguing in a procedural picture i.e. pushing
and popping arguments onto and from the stack.

Someone asked once for a solution of the following problem that is
similar in character to Guidos multimethod but some more general.

def mul(m1,m2):
    def default(m1,m2):
        return "default",1+m1*m2
    def mul_dec(m1,m2):
        return "mul_dec",Decimal(str(m1))*Decimal(str(m2))
    def mul_float(m1,m2):
        return "mul_float",m1*m2
    return (default,mul_dec,mul_float)

The function mul defines the inner functions default, mul_float and
mul_dec. What we want is a unified access to this functions by means of
mul. Guidos solution would decompose mul in three different versions of
mul:

@multimethod(int,float)
def mul(m1,m2):
    return m1*m2

@multimethod(float,float)
def mul(m1,m2):
    return m1*m2


@multimethod(Decimal,Decimal)
def mul(m1,m2):
    return m1*m2

but it is hard to tell, what should be done if no argument tuple
matches.

An attempt like:

@multimethod(object,object)
def mul(m1,m2):
    return 1+m1*m2

would be useless, because there is no concrete match of argument types
onto (object,object).

So I introduced an "external switch" over argument tuples, using a
decorator chain:

@case(None,"default")
@case((float,float),'mul_float')
@case((int,float),'mul_float')
@case((Decimal,Decimal),'mul_dec')

def mul(m1,m2):
    def default(m1,m2):
        return "default",1+m1*m2
    def mul_dec(m1,m2):
        return "mul_dec",Decimal(str(m1))*Decimal(str(m2))
    def mul_float(m1,m2):
        return "mul_float",m1*m2
    return (default,mul_dec,mul_float)

Can You imagine how "case" works internally?

Regards,
Kay




More information about the Python-list mailing list