10

In my Flask-App, I have defined a view-function like this:

@app.route("/some/restricted/stuff")
@login_required
def main():
    return render_template("overview.html",
                       stuff = getstuff() )

Where the decorator gets defined as:

def login_required(something):
    @wraps(something)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return something(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

I basically just copy-pasted that as I've found a few sources where this code is used, but not explained.

It's pretty easy to understand WHAT that code does: It allows me to use a decorator that gets called after app.route() and before main() for each request, allowing me to do stuff like check for an active login and such.

So, as a Flask/Python newbie, I'd just like to know HOW this exactly works, especially:
- What is the argument "something"? Is that the request?!
- What are the args and kwargs (keyword arguments?)?
- Why do I have to wrap a method INSIDE a method to use this as a decorator?
- Is this only usable with flask? Are there other situations something like that could come in handy?

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
MrTrustworthy
  • 416
  • 1
  • 4
  • 14
  • 1
    _first off: This is not a question that is critically relevant to a project. I just like to know how something works_: IMO, "explaining why something happens" are better questions than "What's wrong with my code". However, if you're unsure about how decorators work, try some of the answers to [this question](http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python/1594484) first. – Mark Hildreth Nov 05 '13 at 19:59
  • It is preferred if you can post separate questions instead of combining your questions into one. That way, it helps the people answering your question and also others hunting for at least one of your questions. – double-beep Apr 19 '19 at 10:54

3 Answers3

30

Welcome to Python! That's a lot of great questions. Let's take them one at a time. Also, just a point of fair warning. This topic makes your head spin for awhile before it all clicks together.

For reference, here is your example decorator and function being decorated:

# Decorator Function
def login_required(something):
    @wraps(something)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return something(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

# Function Being Decorated
@app.route("/some/restricted/stuff")
@login_required
def main():
    return render_template("overview.html",
                       stuff = getstuff() )

What is the argument "something"? Is that the request?!

To answer this question, we first have to answer what a decorator is. The answer can vary a bit based on what type of object that you are decorating. In this case, as you are decorating a function, you can think of a decorator as a method/function which allows a programmer to modified the behavior of the another function.

With that out of the way, we can answer your question. "something" is the function/method you are going to decorate. Yes, it is a function that takes another function as an argument.

Let's change the language of your decorator function to make this more clear:

def login_required(function_to_wrap):
    @wraps(function_to_wrap)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return function_to_wrap(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

What are the args and kwargs?

The short answer is that this is Python's way of allowing the parameters programmers to write functions/methods that take a variable number of keyword & non-keyword arguments.

Normally, when you write a function, you specify the parameters explicitly. For example:

def add_these_numbers(number_1, number_2):
    return number_1 + number_2

That is not, however, the only way of doing things. You can also use the *args or **kargs to accomplish the same thing:

def add_these_numbers(*args):
    return args[0] + args[1]

def add_these_numbers_too(**kwargs):
    return kwargs['first_number'] + kwargs['second_number']

As it pertains to your question *args/**kwargs are commonly used in decorators because decorators are often applied to a variety of methods which will take a wide variety of parameters.

Using args/**kwargs allows your decorator method to pass what method were originally required for the method through the decorator function. If that makes your head spin, let me know and I'll try to clarify.

Let's change main() so that this is more clear:

# Function Being Decorated
@app.route("/some/restricted/stuff")
@login_required
def main(html_template):
    return render_template(html_template, stuff = getstuff())

Why do i have to wrap a method INSIDE a method to use this as a decorator?

This is the most tricky part of understanding decorators in my opinion. The key is understanding that at its core, a decorator takes over the name of the original function.

The easiest way to understand this is to apply the decorator without using the handy @ syntax. The following are equivalent:

@login_required
def main():
    ....

main = login_required(main)

Hold on to your horses, this is where is gets AWESOME! What both of these code fragments tell Python is, "the word 'main' should will no longer refer to the main() function, but to the results login_required() function when it was passed the original main() function as a parameter.

WAT!?

Yes. A call to main() now refers to the results of the call to login_required(main()). This is also why login_required returns nested function. The new main() must still be a function, just like the old one was.

The difference is that now the new main function is really an instance of wrap(), customized by the parameter passed to login_required().

So... effectively main() is now equivalent to the following:

def main(*args, **kwargs):
    if "some_admin_name" in session:
        return predecorator_main_function(*args, **kwargs)
    else:
        flash("\"You shall not pass!\" - Gandalf")
        return redirect(url_for("login"))

Is this only usable with Flask? Are there other situations something like that could come in handy?

Definitely not! Decorators are one of the many super-awesome features built right into Python. Decorators are useful in any situation where you want to make modifications (relatively small in my opinion) to existing functions/methods when you don't want to create additional functions to avoid code-duplication

eikonomega
  • 1,971
  • 17
  • 28
  • You are my hero for today! after reading through cryptic documentation and examples, this is by far the most understandable piece of text regarding decorators AND "methods = objects" (didn't really get the point of that before...) I've seen so far. Had to read it once and suddenly a lot regarding python seems to make sense now :) Thank you very much, and have a good evening :) – MrTrustworthy Nov 06 '13 at 00:18
  • 1
    @MrTrustworthy So glad that it helped. This is definitely one of the trickier topics within the Python world. :) – eikonomega Nov 08 '13 at 14:52
  • Awesome explanation which also went through a bunch of other concepts, well done! – Andreas Aug 16 '17 at 12:04
1

Intro to decorators: you can read it here.

What are the args and kwargs (keyword arguments?)?

args when you're not sure how many arguments might be passed to your function.

random(*args)
random('d', 'c', 'b')
random('a', 'e', 'f','z')

kwargs: named arguments. Basically as above, it's like you're passing a dictionary.

random(**kwargs)
random(a="a", b='test', c='ab')

Why do i have to wrap a method INSIDE a method to use this as a decorator?

Check the link decorator link.

Is this only usable with flask? Are there other situations something like that could come in handy?

No, decorators are available in Python in general, it's not limited to flask only. You can use it for logging, synchronization and a lot of other use. In Django, you can allow a post or get request using a decorator, or for login_required and so on...

Community
  • 1
  • 1
  • regarding the *args/**kwargs: i understand how that's used in python, but i didn't get their purpose in a decorator. my bad, should have made my question more precise at that point. still, thank you for your answer :) – MrTrustworthy Nov 06 '13 at 00:11
0

This:

@deco
def foo():
    return 100

Is equal to this:

def temp_foo():
    return 100
foo = deco(temp_foo)

The definition for login_required takes the old main as something, wraps a new function around it, and returns that one.

Lynn
  • 10,425
  • 43
  • 75