0

I'm reading this answer to understand what decorators are and what can they do, from which my question emerges. The author provide a bonus snippet, which can make any decorator accept generically any argument, and this snippet really confused me:

def decorator_with_args(decorator_to_enhance):
    def decorator_maker(*args, **kwargs):
        def decorator_wrapper(func):
            return decorator_to_enhance(func, *args, **kwargs)
        return decorator_wrapper
    return decorator_maker

@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print "Decorated with", args, kwargs
        return func(function_arg1, function_arg2)
    return wrapper

@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print "Hello", function_arg1, function_arg2

decorated_function("Universe and", "everything")

This outputs:

Decorated with (42, 404, 1024) {}
Hello Universe and everything

My question is: What exactly does decorator_with_args do?

Seems that it takes a decorator as its argument, wrap it with a decorator maker that accept arbitrary arguments, which are passed to the argument decorator, and return that decorator maker. This means decorator_with_args actually turns a decorator into a decorator maker. Sounds impossible, right? Anyway, I think it's tricky to tell its function.

And yes, the original code contains many comments, but I failed to get the answer from them, so I removed them to make the code shorter and cleaner.


Community
  • 1
  • 1
nalzok
  • 14,965
  • 21
  • 72
  • 139
  • Don't think of decorators as anything special. The line `@decoraotor_with_args` is syntactic sugar for calling `decorated_decorator = decorator_with_args(decorated_decorator)` after the definition of `decorated_decorator`. Keep that in mind, and that functions are objects that can be passed around in Python, and you should be able to find the answer on your own. – Phillip Jul 21 '16 at 14:34
  • @Phillip In fact, I've been staring at this snippet for more than 5 hours. Maybe be I'm on the wrong way at the very beginning. See: http://codereview.stackexchange.com/q/135482/96221 – nalzok Jul 21 '16 at 14:47
  • @Phillip Here is my current idea: What it does is turning a function whose argument list is `(func, *args, **kwargs)` to another function whose argument list is `(*args, **kwargs)`. However, the "lost" argument `func` is handled in some way I can't understand now. – nalzok Jul 21 '16 at 15:00
  • Another hint, if you write `@foo(bar, baz, qux)`, then Python will call `foo` with arguments `bar, baz, qux`. `foo` must return a callable that will be used as the actual decorator. That is, the returned object must be callable and accept a function as first argument and return an altered function. (It helps tremendously to add print statements to the top of *each* of the functions, i.e., `decorator_with_args`, `decorator_maker`, `decorator_wrapper`, ..., that just output the name of the function and its arguments.) – Phillip Jul 21 '16 at 18:51
  • @Phillip Got that: Function call has higher procedure than the `@` syntax sugar. I've write an answer below, and could you tell me is it right? (My only prior programming experience is in C, and functional programming is a little tricky for me at the beginning.) – nalzok Jul 22 '16 at 00:51

1 Answers1

1

In short, decorator_with_args turns a function call like this:

function(func, *args, **kwargs)

to this form:

function(*args, **kwargs)(fund)

Note that both function call return another function.

nalzok
  • 14,965
  • 21
  • 72
  • 139