flask 上下文管理
前引
在了解flask上下文管理機制之前,先來一波必知必會的知識點。
面向對象雙下方法
首先,先來聊一聊面向對象中的一些特殊的雙下劃線方法,比如__call__、__getattr__系列、__getitem__系列。
__call__
這個方法相信大家并不陌生,在單例模式中,我們可能用到過,除此之外,還想就沒有在什么特殊場景中用到了。我們往往忽視了它一個很特殊的用法:對象object+()或者類Foo()+()這種很特殊的用法。在Flask上下文管理中,入口就是使用了這種方式。
__getitem__系列
使用這個系列的方法時,我們最大的印象就是調用對象的屬性可以像字典取值一樣使用中括號([])。使用中括號對對象中的屬性進行取值、賦值或者刪除時,會自動觸發對應的__getitem__、__setitem__、__delitem__方法。
class Foo(object):def __init__(self):self.name = "boo"def __getitem__(self, item):print("調用__getitem__了")if item in self.__dict__:return self.__dict__[item]def __setitem__(self, key, value):print("調用__setitem__方法了")self.__dict__[key] = valuedef __delitem__(self, key):print("調用__delitem__")del self.__dict__[key]foo = Foo() ret = foo["name"] # print(ret) # 輸出 調用__getitem__了 boo foo["age"] = 18 # print(foo["age"]) # 輸出 調用__setitem__方法了 調用__getitem__了 18 del foo["age"] # 輸出 調用__delitem____getattr__系列
使用對象取值、賦值或者刪除時,會默認的調用對應的__getattr__、__setattr__、__delattr__方法。
對象取值時,取值的順序為:先從__getattribute__中找,第二步從對象的屬性中找,第三步從當前類中找,第四步從父類中找,第五步從__getattr__中找,如果沒有,直接拋出異常。
class Foo(object):def __init__(self):self.name = "boo"def __getattr__(self, item):print("調用__getattr__了")def __setattr__(self, key, value):print("調用__setattr__方法了")def __delattr__(self, item):print("調用__delattr__")foo = Foo() ret = foo.xxx # 輸出 調用__getattr__了 foo.age = 18 # 調用__setattr__方法了 del foo.age # 輸出 調用__delattr__偏函數
再來說說Python中的偏函數
python中有一個小工具包functools,這個包中包含了幾個在很有用的小功能,比如:wraps:在使用裝飾器時,使用這個方法可以保護函數的元信息。reduce:一個合并序列項為一個單一值的小方法。還有一個就是偏函數: partial??
一句話來總結partial的作用,固定函數中的一些參數,返回一個新的函數,方便調用
from functools import partialclass 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())仿照flask來實現一個更復雜的
from functools import partialclass HttpRequest(object):def __init__(self):self.method = "GET"self.body = b"name=abc@age=123"class Foo(object):def __init__(self):self.request = HttpRequest()self.session = {"login":True,"is_super":False}foo = Foo()def func(args):return getattr(foo,args)re_func = partial(func,'request') se_func = partial(func,'session')class LocalProxy(object):def __init__(self,local):self._local = localdef _get_current_object(self):return self._local()def __getitem__(self, item):return getattr(self._get_current_object(),item)request = LocalProxy(re_func) ret = request._get_current_object().method print(ret)ret = request['method'] print(ret)session = LocalProxy(se_func) print(session._get_current_object())threading.local
再來說一說threading.local方法
在多線程中,同一個進程中的多個線程是共享一個內存地址的,多個線程操作數據時,就會造成數據的不安全,所以我們就要加鎖。但是,對于一些變量,如果僅僅只在本線程中使用,怎么辦?
方法一,可以通過全局的字典,key為當前線程的線程ID,value為具體的值。
方法二,使用threading.local方法
threading.local 在多線程操作時,為每一個線程創建一個值,使得線程之間各自操作自己 的值,互不影響。
import time import threadinglocal = threading.local()def func(n):local.val = ntime.sleep(5)print(n)for i in range(10):t = threading.Thread(target=func,args=(i,))t.start()# 結果輸出 0--9?自定義使用threading.local的功能
import time import threading # from threading import current_thread as getcurrent from greenlet import getcurrentclass Local(object):def __init__(self):object.__setattr__(self,"_storage",{})def __setattr__(self, key, value):# ident = threading.get_ident()ident = getcurrent() # 定制粒度更細的if ident in self._storage:self._storage[ident][key] = valueelse:self._storage[ident] = {key:value}def __getattr__(self, item):# ident = threading.get_ident()ident = getcurrent()return self._storage[ident][item]local = Local()def func(n):local.val = ntime.sleep(2)print(local.val)for i in range(10):t = threading.Thread(target=func,args=(i,))t.start()仿照flask用棧來實現自定義threading.local的存取
from greenlet import getcurrentclass Local(object):def __init__(self):object.__setattr__(self,"_storage",{})def __setattr__(self, key, value):# ident = threading.get_ident()ident = getcurrent() # 定制粒度更細的if ident in self._storage:self._storage[ident][key] = valueelse:self._storage[ident] = {key:value}def __getattr__(self, item):# ident = threading.get_ident()ident = getcurrent()return self._storage[ident][item]class LocalStack(object):def __init__(self):self.local = Local()def push(self,item):self.local.stack = []self.local.stack.append(item)def pop(self):return self.local.stack.pop()def top(self):return self.local.stack[-1]_local_stack = LocalStack() _local_stack.push(55) print(_local_stack.top()) # 取棧頂元素預熱完畢,來一波真正的操作,不過在正戲上演之前,先來提一嘴,flask與其他python框架比如(Django、tornado)在整個請求生命周期中對于數據的管理機制的不同。django、tornado是通過傳參的形式傳遞數據,而flask是通過其特有的上下文管理機制來管理數據的。
下面進入我們今天的正題----flask的上下文管理機制。在flask中,上下文管理機制分為兩個大的部分:請求上下文和應用上下文。
flask的上下文管理機制
接下來,從以下三個大的方面分別探討flask的兩大上下文管理機制。
先來一個最簡單的flask版的Hello World
from flask import Flaskapp = Flask(__name__)@app.route('/') def index():return "Hello World"if __name__ == '__main__':app.run()啟動一個flask項目時,會先執行app.run()方法,這是整個項目的入口,執行run方法時,接著執行werkzeug模塊中的run_simple
werkzeug中觸發調用了Flask的__call__方法
?
?請求進來時
觸發執行__call__方法,__call__方法的邏輯很簡單,直接執行wsgi_app方法,將包含所有請求相關數據和一個響應函數傳進去。
def application(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])return '<h1>Hello, web!</h1>'上面的application()函數就是符合WSGI標準的一個HTTP處理函數,它接收兩個參數:environ:一個包含所有HTTP請求信息的dict對象;start_response:一個發送HTTP響應的函數。在application()函數中,調用:start_response('200 OK', [('Content-Type', 'text/html')])備注:__call__是一個符合wsgi標準的函數
執行wsgi_app方法
def wsgi_app(self, environ, start_response):ctx = self.request_context(environ)error = Nonetry:try:ctx.push()response = self.full_dispatch_request()except Exception as e:error = eresponse = self.handle_exception(e)except:error = sys.exc_info()[1]raisereturn response(environ, start_response)finally:if self.should_ignore_error(error):error = Nonectx.auto_pop(error)第一步先執行了一個request_context的方法,將environ傳進去,最后返回一個RequestContext類的對象,被ctx的變量接收(ctx=request_context(environ))
def request_context(self, environ):"""Create a :class:`~flask.ctx.RequestContext` representing aWSGI environment. Use a ``with`` block to push the context,which will make :data:`request` point at this request.See :doc:`/reqcontext`.Typically you should not call this from your own code. A requestcontext is automatically pushed by the :meth:`wsgi_app` whenhandling a request. Use :meth:`test_request_context` to createan environment and context instead of this method.:param environ: a WSGI environment"""return RequestContext(self, 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。
緊接著執行ctx.push()方法,這個方法中,在執行請求上下文對象ctx之前先實例化了一個app_context對象,先執行了app_context的push方法,然后才執行_request_ctx_stack對象中的top和_request_ctx_stack.push(self),最后對ctx中的session進行處理。
所以,flask中的應用上下文發生在請求上下文之前。
def push(self): top = _request_ctx_stack.topif top is not None and top.preserved:top.pop(top._preserved_exc)# 在執行request_context請求上下文的push方法時,先執行了app_context應用上下文的push方法app_ctx = _app_ctx_stack.topif 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()# 然后執行請求上下文對象中LocalStack對象的push方法_request_ctx_stack.push(self)# 最后處理sessionif self.session is None:session_interface = self.app.session_interfaceself.session = session_interface.open_session(self.app, self.request)if self.session is None:self.session = session_interface.make_null_session(self.app)但是我們先說請求上下文,在處理完應用上下文的push方法后,緊接著執行了_request_ctx_stack對象的兩個方法。
而這個_request_ctx_stack是LocalStack這個類的對象。_request_ctx_stack = LocalStack()
? LocalStack有沒有很眼熟,沒錯,flask內部使用的機制就是類似于我們上文中自定義的LocalStack的機制,實例化過程中使用了面向對象中的組合概念,self._local = Local(),然后在自身又實現了push、pop、top方法,這三個方法中都是通過反射從Local類的實例化對象中對一個stack屬性進行append、pop、[-1]的操作,所以,Local對象中的stack屬性對應的值一定是一個類似于列表的東西。通過對列表的操作,實現一個類似于棧的存取。
接著聊聊這個Local類,在實例化時,會對每個對象生成一個storage的空字典。我們翻遍整個Local類的源碼,發現內部并沒有實現一個叫stack的方法或者屬性,但是上面我們提到了LocalStack對象會對Local對象中的一個叫stack的東西進行一系列操作。找不到不會報錯嗎?
這就是flask的巧妙之處,通過類的一些魔法方法巧妙的實現了相應的處理。在前引中,提到如果對象中沒有某個屬性,取值時,最終會執行類中的__getattr__方法,然后再做后續的異常處理,flask將所有的對應邏輯都實現在了類的__getattr__方法中,將每一個線程存儲到字典中,在請求進來時,將每一個對應的請求ctx存在一個列表中,使用時直接調用,而不是通過傳參的形式,更體現出了flask框架的輕量級。
處理完_request_ctx_stack后,就該處理session了。
在flask中,處理session時,非常的巧妙,完美的遵循了開閉原則,會先執行session_interface對象的open_session方法,在這個方法中,會先從用戶請求的cookie中獲取sessionid,獲取該用戶之前設置的session值,然后將值賦值到ctx.session中。
處理完session后,ctx.push方法就執行完了,返回到最開始的app.wsgi_app方法中,執行完push方法后,接著執行full_dispatch_request方法,從這個名字中我們也能猜到,這個方法只要是負責請求的分發。
def full_dispatch_request(self):self.try_trigger_before_first_request_functions()try:request_started.send(self)rv = self.preprocess_request()if rv is None:rv = self.dispatch_request()except Exception as e:rv = self.handle_user_exception(e)return self.finalize_request(rv)在full_dispatch_request方法中先執行preprocess_request方法,這個方法,會先執行所有被before_request裝飾器裝飾的函數,然后就通過路由的分發執行視圖函數了(dispatch_request)
執行視圖函數時
在執行視圖函數之前,先執行了before_request,在執行我們的視圖函數。
視圖函數主要處理業務邏輯。在視圖函數中可以調用request對象,進行取值,也可以調用session對象對session的存取。
在整個request的請求生命周期中,獲取請求的數據直接調用request即可,對session進行操作直接調用session即可。request和session都是LocalProxy對象,借助偏函數的概念將對應的值傳入_lookup_req_object函數。先從_request_ctx_stack(LocalStack)對象中獲取ctx(請求上下文對象),再通過反射分別獲取request和session屬性。整個過程中LocalStack扮演了一個全局倉庫的角色,請求進來將數據存取,需要時即去即用。所以,flask實現了在整個請求的生命周期中哪兒需要就直接調用的特色。
request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session'))請求結束前
視圖函數執行完后,dispatch_request執行結束,執行full_dispatch_request方法的返回值finalize_request方法。這個方法中,同樣的,在返回響應之前,先執行所有被after_request裝飾器裝飾的函數。
---->finalize_request ------> process_response def process_response(self, response):ctx = _request_ctx_stack.topbp = ctx.request.blueprintfuncs = ctx._after_request_functionsif 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執行process_response過程中,執行完after_request后,然后,執行session的save_session方法。將內存中保存在ctx.session的值取到后,json.dumps()序列化后,寫入響應的cookie中(set_cookie),最后返回響應。
def save_session(self, app, session, response):domain = self.get_cookie_domain(app)path = self.get_cookie_path(app)# If the session is modified to be empty, remove the cookie.# If the session is empty, return without setting the cookie.if not session:if session.modified:response.delete_cookie(app.session_cookie_name,domain=domain,path=path)return# Add a "Vary: Cookie" header if the session was accessed at all.if session.accessed:response.vary.add('Cookie')if not self.should_set_cookie(app, session):returnhttponly = self.get_cookie_httponly(app)secure = self.get_cookie_secure(app)samesite = self.get_cookie_samesite(app)expires = self.get_expiration_time(app, session)val = self.get_signing_serializer(app).dumps(dict(session))# set_cookie將session寫入響應的cookie中response.set_cookie(app.session_cookie_name,val,expires=expires,httponly=httponly,domain=domain,path=path,secure=secure,samesite=samesite)返回響應后,自動的調用ctx.auto_pop(error),將Local中存儲的ctx對象pop掉,整個請求結束。
請求上下文的執行流程:
?
?
?
應用上下文
與請求上下文類似,當請求進來時,先實例化一個AppContext對象app_ctx,在實例化的過程中,提供了兩個有用的屬性,一個是app,一個是g。self.app就是傳入的全局的app對象,self.g是一個全局的存儲值的對象。接著將這個app_ctx存放到LocalStack()。
class AppContext(object):
def?__init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
視圖函數中,我們就可以調用app對象和g對象,如果我們使用藍圖構建我們的項目時,在每一個直接引用app就會造成循環引用的異常,這時,應用上下文就會顯得非常有用,我們可以直接調用current_app就可以在整個生命周期中使用我們的app對象了。比如使用我們的配置項:current_app.config
current_app = LocalProxy(_find_app)最后,當視圖函數執行結束后,從storage中pop掉app_ctx對象。
from flask import Flask,request,session,gapp = Flask(__name__) # type:Flask@app.before_request def auth_demo():g.val = 123@app.route('/') def index():print(g.val)return "Hello World"if __name__ == '__main__':app.run()?
總結:flask中的上下文管理機制分為請求上下文和應用上下文兩大方面,通過上下文的管理機制,實現了即去即用的一個特色。
轉載于:https://www.cnblogs.com/wangyao2317072926/p/9931968.html
總結
以上是生活随笔為你收集整理的flask 上下文管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CISC-235 Fall 2018 A
- 下一篇: 解决Android Studio编译后安