關於我
一個有思想的程序猿,終身學習實踐者,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。
Github:github.com/hylinux1024
微信公衆號:終身開發者(angrycode)linux
Flask
中全局變量有current_app
、request
、g
和session
。不過須要注意的是雖然標題是寫着全局變量,但實際上這些變量都跟當前請求的上下文環境有關,下面一塊兒來看看。git
current_app
是當前激活程序的應用實例;request
是請求對象,封裝了客戶端發出的HTTP
請求中的內容;g
是處理請求時用做臨時存儲的對象,每次請求都會重設這個變量;session
是用戶會話,用於存儲請求之間須要保存的值,它是一個字典。github
應用程序上下文可用於跟蹤一個請求過程當中的應用程序實例。能夠像使用全局變量同樣直接導入就可使用 (注意這個變量並非全局變量)。
Flask
實例有許多屬性,例如config
能夠Flask
進行配置。數據庫
通常在建立Flask
實例時flask
from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
...
複製代碼
一般不會直接導入app
這個變量,而是使用經過導入current_app
這個應用上下文實例代理api
from flask import current_app
複製代碼
Flask
應用在處理客戶端請求(request
)時,會在當前處理請求的線程中推送(push
)一個上下文實例和請求實例(request
),請求結束時就會彈出(pop
)請求實例和上下文實例,因此current_app
和request
是具備相同的生命週期的,且是綁定在當前處理請求的線程上的。服務器
若是一個沒有推送上下文實例就直接使用current_app
,會報錯微信
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that
needed to interface with the current application object in some way.
To solve this, set up an application context with app.app_context().
複製代碼
若是要直接使用current_app
就要手動推送(push
)應用上下文實例,從上面的錯誤信息能夠知道,可使用with
語句,幫助咱們push
一個上下文實例session
def create_app():
app = Flask(__name__)
with app.app_context():
init_db()
return app
複製代碼
須要注意的是current_app
是「線程」本地變量,因此current_app
須要在視圖函數或命令行函數中使用,不然也會報錯。數據結構
要理解這一點就要對服務器程序工做機制有所瞭解。通常服務器程序都是多線程程序,它會維護一個線程池,對於每一個請求,服務器會從線程池中獲取一個線程用於處理這個客戶端的請求,而應用的current_app
、request
等變量是「線程」本地變量,它們是綁定在「線程」中的(至關於線程本身獨立的內存空間),因此也在線程環境下才可以使用。
在Flask
中是否也是經過線程本地變量來實現的呢?這個問題咱們在後面的工做原理一節會給出答案。
若要在應用上下文中存儲數據,Flask
提供了g
這個變量爲咱們達到這個目的。g
其實就是global
的縮寫,它的生命週期是跟應用上下文的生命週期是同樣的。
例如在一次請求中會屢次查詢數據庫,能夠把這個數據庫鏈接實例保存在當次請求的g
變量中,在應用上下文生命週期結束關閉鏈接。
from flask import g
def get_db():
if 'db' not in g:
g.db = connect_to_database()
return g.db
@app.teardown_appcontext
def teardown_db():
db = g.pop('db', None)
if db is not None:
db.close()
複製代碼
request
封裝了客戶端的HTTP
請求,它也是一個線程本地變量。
沒有把這個變量放在處理api
請求的函數中,而是經過線程本地變量進行封裝,極大地方便使用,以及也使得代碼更加簡潔。
request
的生命週期是跟current_app
是同樣的,從請求開始時建立到請求結束銷燬。一樣地Flask
在處理請求時就會push
一個request
和應用上下文的代理實例,而後纔可使用。若是沒有push
就使用就會報錯
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.
複製代碼
一般這個錯誤在測試代碼中會常常遇到,若是須要在單元測試中使用request
,可使用test_client
或者在with
語句中使用test_requet_context()
進行模擬
def generate_report(year):
format = request.args.get('format')
...
with app.test_request_context(
'/make_report/2017', data={'format': 'short'}):
generate_report()
複製代碼
前面講到若是在一個請求期間共享數據,可使用g
變量,但若是要在不一樣的請求(request
)之間共享數據,那就須要使用session
,這是一個私有存儲的字典類型。能夠像操做字典同樣操做session
。
session
是用戶會話,能夠保存請求之間的數據。例如在使用login
接口進行用戶登陸以後,把用戶登陸信息保存在session
中,而後訪問其它接口時就能夠經過session
獲取到用戶的登陸信息。
@app.route('/login')
def login():
# 省略登陸操做
...
session['user_id']=userinfo
@app.route('/show')
def showuser():
# 省略其它操做
...
userid = request.args.get('user_id')
userinfo = session.get(userid)
複製代碼
咱們知道Flask
在處理一個請求時,wsgi_app()
這個方法會被執行。而在Flask
的源碼內部request
和current_app
是經過_request_ctx_stack
這個棧結構來保存的,分別爲
# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)
複製代碼
須要注意最新的版本源碼會有些不一樣request
和current_app
分別是有兩個棧結構來存儲:_request_ctx_stack
和_app_ctx_stack
。但新舊代碼思路是差很少的。
最新的源碼裏,全局變量的定義
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))
複製代碼
其中_find_app
和_lookup_app_object
方法是這樣定義的
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)
複製代碼
能夠看到current_app
和g
是LocalProxy
經過_app_ctx_stack.top
進行封裝的。request
和session
是_request_ctx_stack
的封裝。LocalProxy
是werkzeug
庫中local
對象的代理。LocalStack
顧名思義是一個實現了棧的數據結構。
前面提到全局變量是跟線程綁定的,每一個線程都有一個獨立的內存空間,在A
線程設置的變量,在B
線程是沒法獲取的,只有在A
線程中才能獲取到這個變量。這個在Python
的標準庫有thread locals
的概念。
然而在Python
中除了線程外還有進程和協程能夠處理併發程序的技術。因此爲了解決這個問題Flask
的依賴庫werkzeug
就實現了本身的本地變量werkzeug.local
。它的工做機制跟線程本地變量(thread locals
)是相似的。
要使用werkzug.local
from werkzeug.local import Local, LocalManager
local = Local()
local_manager = LocalManager([local])
def application(environ, start_response):
local.request = request = Request(environ)
...
application = local_manager.make_middleware(application)
複製代碼
在application(environ,start_response)
方法中就把封裝了請求信息的request
變量綁定到了local變量中。而後在相同的上下文下例如在一次請求期間,就能夠經過local.request
來獲取到這個請求對應的request
信息。
同時還能夠看到LocalManager
這個類,它是本地變量管理器,它能夠確保在請求結束以後及時的清理本地變量信息。
在源碼中對LocalManager
是這樣註釋的
Local objects cannot manage themselves. For that you need a local manager. You can pass a local manager multiple locals or add them later by appending them to
manager.locals
. Every time the manager cleans up, it will clean up all the data left in the locals for this context.
Local
不能自我管理,須要藉助LocalManager
這個管家來實現請求結束後的清理工做。
current_app
、g
、request
和session
是Flask
中常見4個全局變量。current_app
是當前Flask
服務運行的實例,g
用於在應用上下文期間保存數據的變量,request
封裝了客戶端的請求信息,session
表明了用戶會話信息。