開胃小菜
在了解flask上下文管理機制之前,先來一波必知必會的知識點。
首先,先來聊一聊面向?qū)ο笾械囊恍┨厥獾碾p下劃線方法,比如call、getattr系列、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

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

執(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í)行流程:
