Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a self-written decorator (like @login_required) actually doing? [closed]

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?

like image 424
MrTrustworthy Avatar asked Nov 05 '13 19:11

MrTrustworthy


People also ask

What is @login_required in Python?

auth. decorators login_required Example Python Code. Django's login_required function is used to secure views in your web applications by forcing the client to authenticate with a valid logged-in User.

What is a decorator explain it with an example?

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.

Can decorators be chained?

Chaining decorators means applying more than one decorator inside a function. Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates several effects together. It is also known as nested decorators in Python.


1 Answers

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

like image 165
eikonomega Avatar answered Nov 15 '22 13:11

eikonomega