Python Decorator Self-firing
Question: I am fairly new to Python and have been learning about decorators. After
messing around with Flask, I am trying to write some code that simulates their
route handler/decorators, just to understand how decorators (with arguments)
work.

In the code below, the route decorator seems to call itself once the script
runs. My question is, how is it possible that `app.route()` gets called when i
run this script, and what is really happening here? Notice i don't call my
`index()` function anywhere directly.

    
    
    # test.py
    
    class Flask(object):
    
        def __init__(self, name):
            self.scriptname = name
    
        def route(self, *rargs, **kargs):
            args = list(rargs)
            if kargs:
                print(kargs['methods'])
            def decorator(f):
                f(args[0])
            return decorator
    
    app = Flask(__name__)
    
    @app.route("/", methods = ["GET","PUT"])
    def index(rt):
        print('route: ' + rt)
    

the above prints this in my terminal:

    
    
    $ python test.py
    ['GET', 'PUT']
    route: /
    

Any insight would be appreciated.


Answer: `@app.route("/", methods = ["GET","PUT"])` is an executable statement: it
calls the `route()` method of the app object. Since it's at module level, it
will be executed when the script is imported.

Now, the _result_ of calling `app.route(...)` is a function, and because
you've used the `@` to mark it as a decorator, that function will wrap
`index`. Note that the syntax is just a shortcut for this:

    
    
    index = app.route(...)(index)
    

in other words, Python will call the function returned by `app.route()` with
`index` as a parameter, and store the result as the new `index` function.

However, you're missing a level here. A normal decorator, without params, is
written like this:

    
    
    @foo
    def bar()
       pass
    

and when the module is imported, `foo()` is run and returns a function that
wraps `bar`. But you're calling your `route()` function _within_ the decorator
call! So actually your function needs to _return a decorator function_ that
itself returns a function that wraps the original function... headscratching,
to be sure.

Your `route` method should look more like this:

    
    
    def route(self, *rargs, **kargs):
        args = list(rargs)
        if kargs:
            print(kargs['methods'])
        def decorator(f):
            def wrapped(index_args):
                f(args[0])
            return wrapped
        return decorator
    

