• Python装饰器-装饰流程,执行顺序


    最近看到一个关于Flask的CTF(RealWorld CTF 2018 web题bookhub)文章
    其中的一个trick是装饰器的顺序问题,就想写篇博客回顾下装饰器~

    首先强烈推荐很久之前看的一篇博文
    (翻译)理解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

    Flask @login_require

    从上面的几个例子我们应该大概了解了,多个装饰器进行装饰以及执行的顺序
    我们来看这道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:usersayidesktop	est256.py(17)<module>()
    -> @loginRequire
    (Pdb) s
    > c:usersayidesktop	est256.py(18)<module>()
    -> @app.route('/up')
    (Pdb) s
    > c:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.py(1252)route()-><function Fla...at 0x0376F978>
    -> return decorator
    (Pdb) s
    --Call--
    > c:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.py(1248)decorator()
    -> def decorator(f):
    (Pdb) f
    <function up at 0x0376F9C0>
    (Pdb) s
    > c:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.py(1249)decorator()
    -> endpoint = options.pop('endpoint', None)
    (Pdb) s
    > c:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.py(1250)decorator()
    -> self.add_url_rule(rule, endpoint, f, **options)
    (Pdb) f
    <function up at 0x0376F9C0>
    #===================================================================================#
    		上方up 下方down
    #===================================================================================#
    > c:usersayidesktop	est256.py(22)<module>()
    -> @app.route('/down')
    (Pdb) s
    > c:usersayidesktop	est256.py(23)<module>()
    -> @loginRequire
    (Pdb) s
    --Call--
    > c:usersayidesktop	est256.py(6)loginRequire()
    -> def loginRequire(function_to_decorate):
    (Pdb) s
    > c:usersayidesktop	est256.py(7)loginRequire()
    -> print("loginRequire初始化")
    (Pdb) s
    loginRequire初始化
    > c:usersayidesktop	est256.py(9)loginRequire()
    -> def wrapperTwo():
    (Pdb) s
    > c:usersayidesktop	est256.py(13)loginRequire()
    -> return wrapperTwo
    (Pdb) s
    --Return--
    > c:usersayidesktop	est256.py(13)loginRequire()-><function log...at 0x0071C468>
    -> return wrapperTwo
    (Pdb) s
    --Call--
    > c:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.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:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.py(1249)decorator()
    -> endpoint = options.pop('endpoint', None)
    (Pdb) s
    > c:usersayi.virtualenvs	est-gq7eoxbqlibsite-packagesflaskapp.py(1250)decorator()
    -> self.add_url_rule(rule, endpoint, f, **options)
    (Pdb) f
    <function up at 0x0376F9C0>
    

    也就是添加路由的时候会选择丢失外层的路由,只装饰route下方的函数
    在add_url_rule中,有这段注释:

    Basically this example::
    
        @app.route('/')
        def index():
        	pass
    
    Is equivalent to the following::
    
        def index():
        	pass
        app.add_url_rule('/', 'index', index)
    
  • 相关阅读:
    菜根谭#298
    菜根谭#297
    菜根谭#296
    菜根谭#295
    菜根谭#294
    菜根谭#293
    菜根谭#292
    菜根谭#291
    菜根谭#290
    菜根谭#289
  • 原文地址:https://www.cnblogs.com/bay1/p/10979285.html
Copyright © 2020-2023  润新知