decorator peculiarity

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Diez B. Roggisch

    #1

    decorator peculiarity

    Hi,

    I just wrote my first decorator example - and I already love them.

    However I've encountered one peculiarity that strikes me odd:

    When one writes a decorated function like this:

    @decorate
    def foo():
    pass

    the function decorate usually looks like this:

    def decorate(func):
    def _d(*args, **kwargs):
    do_something()
    # call func
    func(*args, **kwargs)
    return _d

    So the function decorator has to return a function that is bound to the name
    foo in the originating context (module or class) and gets the function
    passed as argument.

    But if I want my decorator to be parametrized, it looks like this:

    @decorate(arg)
    def foo():
    pass

    def decorate(arg):
    def f(func):
    def _d(*args, **kwargs):
    do_something(ar g)
    # call func
    func(*args, **kwargs)
    return _d

    So what happens is that an decorater with arguments is called with these,
    returns a callable that then is called with foo, and the result is stored
    under foo.

    Now why this layer of indirection? I would have supposed that things look
    like this:


    def decorate(func, arg):
    def _d(*args, **kwargs):
    do_something(ar g)
    # call func
    func(*args, **kwargs)
    return _d

    A sideeffect of this behaviour is that for a fully keyword-argumentized
    function I still have to write parentheses:

    @decorate()
    def foo():
    pass

    def decorate(arg=No ne):
    def f(func):
    def _d(*args, **kwargs):
    do_something(ar g)
    # call func
    func(*args, **kwargs)
    return _d

    I don't mind the extra parentheses too much - it just made me wonder what
    the rationale behind this is.

    --
    Regards,

    Diez B. Roggisch
  • Scott David Daniels

    #2
    Re: decorator peculiarity

    Diez B. Roggisch wrote:[color=blue]
    > def decorate(func):
    > def _d(*args, **kwargs):
    > do_something()
    > # call func
    > func(*args, **kwargs)
    > return _d
    > @decorate
    > def foo():
    > pass
    > [T]he function decorator has to return a function that is bound to the name
    > foo in the originating context (module or class) and gets the function
    > passed as argument. If I want my decorator ... parametrized... :
    > @decorate(arg)
    > def foo():
    > pass
    > def decorate(arg):
    > def f(func):
    > def _d(*args, **kwargs):
    > do_something(ar g)
    > # call func
    > func(*args, **kwargs)
    > return _d
    > Now why this layer of indirection?[/color]

    Let's rewrite your example a bit, it may become obvious.
    def decorate(func):
    def replacement(arg spec):
    do_something()
    return func(argspec)
    return replacement
    @decorate
    def foo(argspec): pass
    ---
    def decorate2():
    def _decorate(func) :
    def replacement(arg spec):
    do_something()
    return func(argspec)
    return replacement
    return _decorate
    @decorate()
    def foo(argspec): pass
    ---
    def decorate3(arg):
    def _decorate(func) :
    def replacement(arg spec):
    do_something()
    return func(argspec)
    return replacement
    return _decorate
    @decorate3(arg)
    def foo(argspec): pass
    ---
    Clear now? there is no extra indirection. If you call something on
    the @ line, the _result_of_that _call_ should be a decorator function.
    If you use my curry recipe in the python cookbook, you can use curry
    lower the apparent "indirectio n":

    def decorate4(arg, func):
    def replacement(arg spec):
    do_something()
    return func(argspec)
    return replacement
    @curry(decorate 4, arg)
    def foo(argspec): pass

    By the way, I _do_ think curry is an appropriate name. The Curry-Howard
    isomorphism shows the equivalence of a functions on tuples with single-
    argument functions returning functions. In a functional language, where
    there is no distinction made between a a function with no arg and its
    result, the transformation is clear. With a language like Python, which
    pays attention to when a function is invoked, you are better off
    returning a function which can take the remaining arguments, and
    controlling when the final function is called by returning a function
    that can take all the remaining arguments at once.

    -Scott David Daniels
    Scott.Daniels@A cm.Org

    Comment

    • Diez B. Roggisch

      #3
      Re: decorator peculiarity

      Hi
      [color=blue]
      > Clear now? there is no extra indirection.  If you call something on
      > the @ line, the _result_of_that _call_ should be a decorator function.
      > If you use my curry recipe in the python cookbook, you can use curry
      > lower the apparent "indirectio n":[/color]

      Ok - that makes it clear, thanks.

      --
      Regards,

      Diez B. Roggisch

      Comment

      • Fredrik Lundh

        #4
        Re: decorator peculiarity

        Diez B. Roggisch wrote:
        [color=blue]
        > However I've encountered one peculiarity that strikes me odd:
        >
        > When one writes a decorated function like this:
        >
        > @decorate
        > def foo():
        > pass
        >
        > the function decorate usually looks like this:
        >
        > def decorate(func):
        > def _d(*args, **kwargs):
        > do_something()
        > # call func
        > func(*args, **kwargs)
        > return _d
        >
        > So the function decorator has to return a function that is bound to the name
        > foo in the originating context (module or class) and gets the function
        > passed as argument.
        >
        > But if I want my decorator to be parametrized, it looks like this:
        >
        > @decorate(arg)
        > def foo():
        > pass
        >
        > def decorate(arg):
        > def f(func):
        > def _d(*args, **kwargs):
        > do_something(ar g)
        > # call func
        > func(*args, **kwargs)
        > return _d
        >
        > So what happens is that an decorater with arguments is called with these,
        > returns a callable that then is called with foo, and the result is stored
        > under foo.[/color]

        the decorator itself must be a callable, so to create parameterized decorators,
        you have to create a callable that returns a callable.

        due to some weird obsession with nested functions and lexical scoping, most
        decorator examples use functions inside functions inside functions, but you can
        of course use a good old class instead:

        class decorator:
        def __init__(self, params):
        # save the parameters
        def __call__(self, func):
        # decorate the function
        return func

        @decorator(para ms)
        def func(...):
        pass

        or even:

        class decorator:
        def __init__(self, params):
        # save the parameters
        def __call__(self, func):
        self.func = func
        return self.handler
        def handler(self, *args):
        # do something
        self.func(*args )
        # do something

        </F>



        Comment

        Working...