flask你一定要知道的上下文管理機制

開胃小菜

在了解flask上下文管理機制之前,先來一波必知必會的知識點。
首先,先來聊一聊面向?qū)ο笾械囊恍┨厥獾碾p下劃線方法,比如callgetattr系列、getitem系列。

call系列

這個方法相信大家并不陌生,在單例模式中,我們可能用到過,除此之外,還想就沒有在什么特殊場景中用到了。我們往往忽視了它一個很特殊的用法:對象object+()或者類Foo()+()這種很特殊的用法。在Flask上下文管理中,入口就是使用了這種方式。

getitem系列

使用這個系列的方法時,我們最大的印象就是調(diào)用對象的屬性可以像字典取值一樣使用中括號([])。使用中括號對對象中的屬性進行取值、賦值或者刪除時,會自動觸發(fā)對應(yīng)的__getitem__、__setitem__、__delitem__方法。

class Foo(object):

    def __init__(self):
        self.name = "boo"

    def __getitem__(self, item):
        print("調(diào)用__getitem__了")
        if item in self.__dict__:
            return self.__dict__[item]

    def __setitem__(self, key, value):
        print("調(diào)用__setitem__方法了")
        self.__dict__[key] = value

    def __delitem__(self, key):
        print("調(diào)用__delitem__")
        del self.__dict__[key]


foo = Foo()
ret = foo["name"]
# print(ret)     # 輸出     調(diào)用__getitem__了      boo
foo["age"] = 18
# print(foo["age"])   # 輸出   調(diào)用__setitem__方法了   調(diào)用__getitem__了    18
del foo["age"]   #  輸出  調(diào)用__delitem__

getattr系列

使用對象取值、賦值或者刪除時,會默認的調(diào)用對應(yīng)的__getattr__、__setattr__、__delattr__方法。

對象取值時,取值的順序為:先從__getattribute__中找,第二步從對象的屬性中找,第三步從當(dāng)前類中找,第四步從父類中找,第五步從__getattr__中找,如果沒有,直接拋出異常。

class Foo(object):

    def __init__(self):
        self.name = "boo"

    def __getattr__(self, item):
        print("調(diào)用__getattr__了")

    def __setattr__(self, key, value):
        print("調(diào)用__setattr__方法了")

    def __delattr__(self, item):
        print("調(diào)用__delattr__")


foo = Foo()
ret = foo.xxx    # 輸出     調(diào)用__getattr__了
foo.age = 18    # 調(diào)用__setattr__方法了
del foo.age   #  輸出  調(diào)用__delattr__

再來說說Python中的偏函數(shù)
一句話來總結(jié)partial的作用,固定函數(shù)中的一些參數(shù),返回一個新的函數(shù),方便調(diào)用,舉個例子

from functools import partial

class Foo(object):

    def __init__(self):
        self.request = "request"
        self.session = "session"

foo = Foo()

def func(args):
    return getattr(foo,args)

re_func = partial(func,'request')
se_func = partial(func,'session')

print(re_func())

再來說一說threading.local方法

在多線程中,同一個進程中的多個線程是共享一個內(nèi)存地址的,多個線程操作數(shù)據(jù)時,就會造成數(shù)據(jù)的不安全,所以我們就要加鎖。但是,對于一些變量,如果僅僅只在本線程中使用,怎么辦?

方法一,可以通過全局的字典,key為當(dāng)前線程的線程ID,value為具體的值。

方法二,使用threading.local方法

threading.local 在多線程操作時,為每一個線程創(chuàng)建一個值,使得線程之間各自操作自己 的值,互不影響。

import time
import threading

local = threading.local()

def func(n):
    local.val = n
    time.sleep(5)
    print(n)

for i in range(10):
    t = threading.Thread(target=func,args=(I,))
    t.start()

# 結(jié)果輸出    0--9

正餐

flask的上下文管理機制

啟動一個flask項目時,會先執(zhí)行app.run()方法,這是整個項目的入口,執(zhí)行run方法時,接著執(zhí)行werkzeug模塊中的run_simple

image

werkzeug中觸發(fā)調(diào)用了Flask的call方法

請求進來時

觸發(fā)執(zhí)行call方法,call方法的邏輯很簡單,直接執(zhí)行wsgi_app方法,將包含所有請求相關(guān)數(shù)據(jù)和一個響應(yīng)函數(shù)傳進去。

image

執(zhí)行wsgi_app方法

def wsgi_app(self, environ, start_response):
        
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

第一步先執(zhí)行了一個request_context的方法,將environ傳進去,最后返回一個RequestContext類的對象,被ctx的變量接收(ctx=request_context(environ))

這個ctx對象在初始化時,賦了兩個非常有用的屬性,一個是request,一個是session

def __init__(self, app, environ, request=None):
    self.app = app
    if request is None:
        request = app.request_class(environ)
    self.request = request
    self.url_adapter = app.create_url_adapter(self.request)
    self.flashes = None
    self.session = None

這兩個屬性中request是一個Request()對象,這個對象就是我們在flask中使用的request對象,為我們提供了很多便捷的屬性和方法,比如:request.method、request.form、request.args等等,另一個屬性是session,初始為None。

緊接著執(zhí)行ctx.push()方法,這個方法中,在執(zhí)行請求上下文對象ctx之前先實例化了一個app_context對象,先執(zhí)行了app_context的push方法,然后才執(zhí)行_request_ctx_stack對象中的top和_request_ctx_stack.push(self),最后對ctx中的session進行處理。

所以,flask中的應(yīng)用上下文發(fā)生在請求上下文之前。

在ctx.push方法的執(zhí)行邏輯

def push(self):           
       
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)
            
        # 在執(zhí)行request_context請求上下文的push方法時,先執(zhí)行了app_context應(yīng)用上下文的push方法
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
            
        # 然后執(zhí)行請求上下文對象中LocalStack對象的push方法
        _request_ctx_stack.push(self)

        # 最后處理session
        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)

但是我們先說請求上下文,在處理完應(yīng)用上下文的push方法后,緊接著執(zhí)行了_request_ctx_stack對象的兩個方法。

而這個_request_ctx_stack是LocalStack這個類的對象。_request_ctx_stack = LocalStack()

如果對象中沒有某個屬性,取值時,最終會執(zhí)行類中的getattr方法,然后再做后續(xù)的異常處理,flask將所有的對應(yīng)邏輯都實現(xiàn)在了類的getattr方法中,將每一個線程存儲到字典中,在請求進來時,將每一個對應(yīng)的請求ctx存在一個列表中,使用時直接調(diào)用,而不是通過傳參的形式,更體現(xiàn)出了flask框架的輕量級。

處理完_request_ctx_stack后,就該處理session了。

在flask中,處理session時,非常的巧妙,完美的遵循了開閉原則,會先執(zhí)行session_interface對象的open_session方法,在這個方法中,會先從用戶請求的cookie中獲取sessionid,獲取該用戶之前設(shè)置的session值,然后將值賦值到ctx.session中。

處理完session后,ctx.push方法就執(zhí)行完了,返回到最開始的app.wsgi_app方法中,執(zhí)行完push方法后,接著執(zhí)行full_dispatch_request方法,從這個名字中我們也能猜到,這個方法只要是負責(zé)請求的分發(fā)。

執(zhí)行視圖函數(shù)時

在執(zhí)行視圖函數(shù)之前,先執(zhí)行了before_request,在執(zhí)行我們的視圖函數(shù)。

視圖函數(shù)主要處理業(yè)務(wù)邏輯。在視圖函數(shù)中可以調(diào)用request對象,進行取值,也可以調(diào)用session對象對session的存取。

在整個request的請求生命周期中,獲取請求的數(shù)據(jù)直接調(diào)用request即可,對session進行操作直接調(diào)用session即可。request和session都是LocalProxy對象,借助偏函數(shù)的概念將對應(yīng)的值傳入_lookup_req_object函數(shù)。先從_request_ctx_stack(LocalStack)對象中獲取ctx(請求上下文對象),再通過反射分別獲取request和session屬性。整個過程中LocalStack扮演了一個全局倉庫的角色,請求進來將數(shù)據(jù)存取,需要時即去即用。所以,flask實現(xiàn)了在整個請求的生命周期中哪兒需要就直接調(diào)用的特色。

request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

請求結(jié)束

視圖函數(shù)執(zhí)行完后,dispatch_request執(zhí)行結(jié)束,執(zhí)行full_dispatch_request方法的返回值finalize_request方法。這個方法中,同樣的,在返回響應(yīng)之前,先執(zhí)行所有被after_request裝飾器裝飾的函數(shù)。

def process_response(self, response):
        
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response

執(zhí)行process_response過程中,執(zhí)行完after_request后,然后,執(zhí)行session的save_session方法。將內(nèi)存中保存在ctx.session的值取到后,json.dumps()序列化后,寫入響應(yīng)的cookie中(set_cookie),最后返回響應(yīng)。

返回響應(yīng)后,自動的調(diào)用ctx.auto_pop(error),將Local中存儲的ctx對象pop掉,整個請求結(jié)束。

甜點

如果感覺上面的源碼分析太難以理解,可以根據(jù)下面的請求上下文的執(zhí)行流程圖來加深印象
請求上下文的執(zhí)行流程:

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容