最近看到一个关于Flask的CTF(RealWorld CTF 2018 web题bookhub)文章
其中的一个trick是装饰器的顺序问题,就想写篇博客回顾下装饰器~html
首先强烈推荐好久以前看的一篇博文
(翻译)理解PYTHON中的装饰器
关于什么是装饰器看这篇文章就行了~
这里主要想写关于多个装饰器的执行流程
示例代码python
# import pdb;pdb.set_trace() def functionOne(function_to_decorate): print("functionOne初始化") def wrapperOne(): pass return wrapperOne def functionTwo(function_to_decorate): print("functionTwo初始化") def wrapperTwo(): pass return wrapperTwo @functionOne @functionTwo def testFunction(): pass # 输出结果 functionTwo初始化 functionOne初始化
从上面咱们能得知: 装饰顺序,就近装饰
而后咱们利用下面的代码进行一步探究
以下咱们得知:执行这段代码,至关于:
首先,将testFunction函数打包给wrapperTwo,因为没有调用,functionTwo总体返回了wrapperTwo,而没有执行
而后,functionOne将wrapperTwo做为参数,打包成wrapperOne
# import pdb;pdb.set_trace() def functionOne(function_to_decorate): print("functionOne初始化") def wrapperOne(): print("第一处"+function_to_decorate.__name__) function_to_decorate() return wrapperOne def functionTwo(function_to_decorate): print("functionTwo初始化") def wrapperTwo(): print("第二处"+function_to_decorate.__name__) function_to_decorate() return wrapperTwo @functionOne @functionTwo def testFunction(): print('index') testFunction() #输出结果 functionTwo初始化 functionOne初始化 第一处wrapperTwo 第二处testFunction index
从上面的第二段代码咱们已经能看出部分执行顺序了
就是 它会优先执行咱们打包好的wrapperOne,由于从起始的testFunction,wrapperTwo都已经打包在wrapperOne
能够说成 执行顺序,就远执行
咱们继续执行下面的代码:
# import pdb;pdb.set_trace() def functionOne(function_to_decorate): print("functionOne初始化") def wrapperOne(): print("第一处"+function_to_decorate.__name__) function_to_decorate() print("wrapperOne") return wrapperOne def functionTwo(function_to_decorate): print("functionTwo初始化") def wrapperTwo(): print("第二处"+function_to_decorate.__name__) function_to_decorate() print("wrapperTwo") return wrapperTwo @functionOne @functionTwo def testFunction(): print('index') testFunction() # 输出结果 functionTwo初始化 functionOne初始化 第一处wrapperTwo 第二处testFunction index wrapperTwo wrapperOne
这个执行顺序可能也困扰了不少人,如今咱们从输出结果看
对照代码,就很容易清楚了,执行到wrapperOne中的function_to_decorate时
其实至关于跳转到了函数wrapperTwo,而后执行wrapperTwo
从上面的几个例子咱们应该大概了解了,多个装饰器进行装饰以及执行的顺序
咱们来看这道CTF题目,咱们首先须要知道的是Flask中路由就是一个装饰
from flask import Flask app = Flask(__name__) app.debug = True # import pdb;pdb.set_trace() # 为了更好的控制输出,自定义了loginRequire装饰器 def loginRequire(function_to_decorate): print("loginRequire初始化") def wrapperTwo(): print("loginRequire装饰成功") print(function_to_decorate.__name__) return function_to_decorate() return wrapperTwo @loginRequire @app.route('/up') def up(): return "装饰路由放在上面!" @app.route('/down') @loginRequire def down(): return "装饰路由放在下面!" if __name__ == '__main__': app.run() # 分别访问两个url输出结果 loginRequire初始化 loginRequire初始化 * Debugger is active! * Debugger PIN: 244-957-971 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [24/Aug/2018 19:01:30] "GET /up HTTP/1.1" 200 - loginRequire装饰成功 down 127.0.0.1 - - [24/Aug/2018 19:01:35] "GET /down HTTP/1.1" 200 -
从输出结果咱们能清楚的看到up的装饰,并无执行装饰器
若是按照咱们上面的分析,不管在上面仍是下面都会执行的啊??只是顺序不一样罢了~
咱们利用pdb来一步步调试查看哪里的问题,部分log以下:
> c:\users\bayi\desktop\test\256.py(17)<module>() -> @loginRequire (Pdb) s > c:\users\bayi\desktop\test\256.py(18)<module>() -> @app.route('/up') (Pdb) s > c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1252)route()-><function Fla...at 0x0376F978> -> return decorator (Pdb) s --Call-- > c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1248)decorator() -> def decorator(f): (Pdb) f <function up at 0x0376F9C0> (Pdb) s > c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1249)decorator() -> endpoint = options.pop('endpoint', None) (Pdb) s > c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1250)decorator() -> self.add_url_rule(rule, endpoint, f, **options) (Pdb) f <function up at 0x0376F9C0> #===================================================================================# 上方up 下方down #===================================================================================# > c:\users\bayi\desktop\test\256.py(22)<module>() -> @app.route('/down') (Pdb) s > c:\users\bayi\desktop\test\256.py(23)<module>() -> @loginRequire (Pdb) s --Call-- > c:\users\bayi\desktop\test\256.py(6)loginRequire() -> def loginRequire(function_to_decorate): (Pdb) s > c:\users\bayi\desktop\test\256.py(7)loginRequire() -> print("loginRequire初始化") (Pdb) s loginRequire初始化 > c:\users\bayi\desktop\test\256.py(9)loginRequire() -> def wrapperTwo(): (Pdb) s > c:\users\bayi\desktop\test\256.py(13)loginRequire() -> return wrapperTwo (Pdb) s --Return-- > c:\users\bayi\desktop\test\256.py(13)loginRequire()-><function log...at 0x0071C468> -> return wrapperTwo (Pdb) s --Call-- > c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1248)decorator() -> def decorator(f): (Pdb) f <function loginRequire.<locals>.wrapperTwo at 0x0071C468>
从上面的执行流程,打印出不断出现的f,咱们能看出,两个顺序的f值不一样
在up中,f=up()
在down中,f=wrapperTwo()
这点符合预期,装饰位置不一样,然而在执行Flask源码 add_url_rule时
如上面log所示,直接添加了f的值
> c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1249)decorator() -> endpoint = options.pop('endpoint', None) (Pdb) s > c:\users\bayi\.virtualenvs\test-gq7eoxbq\lib\site-packages\flask\app.py(1250)decorator() -> self.add_url_rule(rule, endpoint, f, **options) (Pdb) f <function up at 0x0376F9C0>
也就是添加路由的时候会选择丢失外层的路由,只装饰route下方的函数
在add_url_rule中,有这段注释:web
Basically this example:: @app.route('/') def index(): pass Is equivalent to the following:: def index(): pass app.add_url_rule('/', 'index', index)
博客地址shell