Python Decorator Self-firing

纵然是瞬间 提交于 2019-12-20 07:12:20

问题


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.


回答1:


@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



回答2:


Basically... app.route(index, "/", ["GET", "PUT"]) is a function. And this is the function which is going to be called instead of index.

In your code, when you call index(), it calls app.route(index, "/", ["GET", "PUT"]). This starts by printing kargs['methods'], then creates the decorator function:

def decorator(f):
    f(args[0])

This decorator will call the decorated function (f) with one argument, args[0], which here is "/". This prints route: /.

The best explanation of decorators I've found is here: How to make a chain of function decorators?

If you dont want the self-firing, you can define your decorator this way:

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

@app.route("/", methods = ["GET","PUT"])
def index(rt):
    print('route: ' + rt)

However, the rt argument of index will never be used, because route always calls index with args[0] which is always \...



来源:https://stackoverflow.com/questions/19102930/python-decorator-self-firing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!