Search code examples
pythondecorator

Python dynamic decorators - why so many wraps?


So I'm still kind of new to Python decorators - I've used them before, but I've never made my own. I'm reading this tutorial (that particular paragraph) and I don't seem to understand why do we need three levels of functions? Why can't we do something like this:

def decorator(func, *args, **kwargs):
    return func(*args,**kwargs)

Thanks :)


Solution

  • Well, what would happen if you called that decorator on a function?

    @decorator
    def foo(): pass
    

    This code would immediately call foo, which we don't want. Decorators are called and their return value replaces the function. It's the same as saying

    def foo(): pass
    foo = decorator(foo)
    

    So if we have a decorator that calls foo, we probably want to have a function that returns a function that calls foo -- that function that it returns will replace foo.

    def decorator(f):
        def g(*args, **kwargs):
            return f(*args, **kwargs)
        return g
    

    Now, if we want to pass options to the decorator, we can't exactly pass them ins ide-by-side with the function like in your example. There's no syntax for it. So we define a function that returns a parameterized decorator. The decorator it returns will be a closure.

    def argument_decorator(will_I_call_f):
        def decorator(f):
            def g(*args, **kwargs):
                if will_I_call_f: return f(*args, **kwargs)
            return g
        return decorator
    

    so we can do

    decorator = argument_decorator(True)
    @decorator
    def foo(): pass
    

    And Python offers the convenience syntax where you inline the function call:

    @argument_decorator(True)
    def foo(): pass
    

    And all this is syntax sugar for the non-decorator syntax of

    def foo(): pass
    foo = argument_decorator(True)(foo)