问题
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