17

Edit 2: I misunderstood how decorators work. The decorator is run even if the decorated function is not called (even though you might not see it's effects). function = dec(function) would be an equivalent way of showing what a decorator does and obviously in that scenario the dec() function runs without any calls to function().

Edit: Why is my post being down voted with no explanation? How can I fix it? There are multiple answers, one which clearly answers the question. What is the issue?

I've been learning about decorators in python and I think I have a good grasp on them. However I am still slightly confused about how the app.route decorator works in flask. By my understanding the decorator changes the behavior of a function but does not run unless the function is called. So if I have:

@app.route("/")
def hello():
    return "Hello world"

hello()

The hello function will get passed to app.route and whatever behavior the decorator dictates will execute. However in flask apps the function itself never appears to be run (in my above example it is). How is the route function/decorator executed if the function it decorates is never called? I know that app.route essentially stores the "/" along with its corresponding function in a dictionary but I don't understand how this code is ever executed without any calls of the decorated function. I assume it is somehow connected to the app.run at the end of flask apps but I am unclear on how app.run can call the functions you defined.

Edit: to add to what I've shown here. There is an example from this explanation: https://ains.co/blog/things-which-arent-magic-flask-part-1.html That raises the same questions. I would think that hello() needs to be called in order for the route function to do anything.

class NotFlask():
    def __init__(self):
        self.routes = {}

    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f

        return decorator

    def serve(self, path):
        view_function = self.routes.get(path)
        if view_function:
            return view_function()
        else:
            raise ValueError('Route "{}"" has not been 
registered'.format(path))


app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"
RB34
  • 721
  • 3
  • 8
  • 17
  • my understanding, app.route takes parameter "/" and runs and returns a decorator which is decorating hello function. – Vasif Sep 08 '17 at 19:42
  • Where/when is the view function called? Obviously it must be called somewhere. I'm just confused as to where that is. – RB34 Sep 08 '17 at 19:52
  • A decorator doesn't change anything. It simply defines a *new* function and rebinds the original name. Applying `app.route` as a decorator is effectively the same as defining `hello` as a regular function, then using `hello = app.route("/")(hello)`. – chepner Sep 08 '17 at 19:54
  • 1
    The call is made from within the Flask library, not from your code, that is why you don't see the call. The decorator adds your function to a data structure in Flask, and calls it when the inbound url matches the route. You will not see `hello()`, anywhere, it will more likely by something like `handler_fn = find_match_for_url(); handler_fn()`. – PaulMcG Sep 08 '17 at 19:57
  • Similar to : https://stackoverflow.com/questions/40460846/using-flask-inside-class/56106835#56106835 – ZettaCircl May 13 '19 at 06:43

2 Answers2

28

Python decorators like this:

@decorator
def func():
    pass

Can be changed to look like this instead:

def func():
    pass

decorator(func)

Or in other words, they're functions that take functions. In some circumstances, you might not see the effects of the decorator immediately, so it may seem like the decorator itself isn't used until the function it decorates is called, but that's not an actual restriction of Python decorators.

In your code, @app.route("/") is a decorator which adds an endpoint to the app object. It doesn't actually modify any behavior of the function, and is instead sugar to simplify the process. Without the route() decorator, this is how you'd do the equivalent route registration in Flask.

from flask import Flask
app = Flask(_name_)

def hello():
    return "Hello world"

app.add_url_rule("/", "hello", hello)

And if you look at the implementation of the route decorator in Flask, you'll see that this is the equivalent.

def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  This does the same thing as :meth:`add_url_rule`
        but is intended for decorator usage::
            @app.route('/')
            def index():
                return 'Hello World'
        For more information refer to :ref:`url-route-registrations`.
        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
                        Starting with Flask 0.6, ``OPTIONS`` is implicitly
                        added and handled by the standard request handling.
        """
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

So you can see that route adds it to the app's router, so once the Flask app gets a request, it decides how to execute code/view for the endpoint that's been requested.

wkl
  • 77,184
  • 16
  • 165
  • 176
  • 1
    Thanks! The first part of your explanation totally clears things up. I mistakenly thought the decorator didn't run until the function it decorates is called. – RB34 Sep 08 '17 at 20:22
2

Your code snippet is calling the hello function directly. That's why the function is running.

For Flask, you define the routes using decorator functions. When you navigate to a URL that matches the decorator, it will execute the decorated function. In this example, if I navigate to "/" on my webserver, the hello function will execute.

To actually run your server, you need to do the following from the command line (which can also be found in Flask's documentation.

$ export FLASK_APP=hello.py
$ flask run

Where hello.py is the name of the file containing your Python code. You should also remove the direct call to hello() in your file. Then open a browser and navigate to http://localhost:5000/ to see the result.

How do Decorators Work

Decorators are functions that wrap other functions in an attempt to alter the behavior of the sub-function. You can read the very detailed explanation in the Python wiki.

Decorators take in a function as their argument. Typically, decorators run some code before executing the sub-function. For example, if you want to add authentication to certain endpoints of your Flask app, you can use decorators. The decorators check to make sure a user is authenticated to use a resource before the actual code of that resource executes.

@authenticate
def hello():
   return "Hello world"

The authenticate function will run first. And then if the user is authenticated, it will call hello to execute the rest, otherwise, it will send an error back to the user. In the case of Flask's decorators, they are checking to see if an incoming request matches the route you've specified. If it does, then it executes the function. Otherwise, it checks the next route.

The following article is my go to for learning about decorators: https://realpython.com/blog/python/primer-on-python-decorators/

TheF1rstPancake
  • 2,318
  • 17
  • 17