關(guān)于我
一個(gè)有思想的程序猿,終身學(xué)習(xí)實(shí)踐者,目前在一個(gè)創(chuàng)業(yè)團(tuán)隊(duì)任team lead,技術(shù)棧涉及Android、Python、Java和Go,這個(gè)也是我們團(tuán)隊(duì)的主要技術(shù)棧。
Github:https://github.com/hylinux1024
微信公眾號(hào):終身開(kāi)發(fā)者(angrycode)
Flask
中全局變量有
current_app
、
request
、
g
和
session
。不過(guò)需要注意的是雖然標(biāo)題是寫著全局變量,但實(shí)際上這些變量都跟當(dāng)前請(qǐng)求的上下文環(huán)境有關(guān),下面一起來(lái)看看。
current_app
是當(dāng)前激活程序的應(yīng)用實(shí)例;
request
是請(qǐng)求對(duì)象,封裝了客戶端發(fā)出的
HTTP
請(qǐng)求中的內(nèi)容;
g
是處理請(qǐng)求時(shí)用作臨時(shí)存儲(chǔ)的對(duì)象,每次請(qǐng)求都會(huì)重設(shè)這個(gè)變量;
session
是用戶會(huì)話,用于存儲(chǔ)請(qǐng)求之間需要保存的值,它是一個(gè)字典。
0x00 current_app
應(yīng)用程序上下文可用于跟蹤一個(gè)請(qǐng)求過(guò)程中的應(yīng)用程序?qū)嵗?梢韵袷褂萌肿兞恳粯又苯訉?dǎo)入就可以使用
(注意這個(gè)變量并不是全局變量)
。
Flask
實(shí)例有許多屬性,例如
config
可以
Flask
進(jìn)行配置。
一般在創(chuàng)建
Flask
實(shí)例時(shí)
from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
...
通常不會(huì)直接導(dǎo)入
app
這個(gè)變量,而是使用通過(guò)導(dǎo)入
current_app
這個(gè)應(yīng)用上下文實(shí)例代理
from flask import current_app
current_app 的生命周期
Flask
應(yīng)用在處理客戶端請(qǐng)求(
request
)時(shí),會(huì)在當(dāng)前處理請(qǐng)求的線程中推送(
push
)一個(gè)上下文實(shí)例和請(qǐng)求實(shí)例(
request
),請(qǐng)求結(jié)束時(shí)就會(huì)彈出(
pop
)請(qǐng)求實(shí)例和上下文實(shí)例,所以
current_app
和
request
是具有相同的生命周期的,且是綁定在當(dāng)前處理請(qǐng)求的線程上的。
如果一個(gè)沒(méi)有推送上下文實(shí)例就直接使用
current_app
,會(huì)報(bào)錯(cuò)
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
就要手動(dòng)推送(
push
)應(yīng)用上下文實(shí)例,從上面的錯(cuò)誤信息可以知道,可以使用
with
語(yǔ)句,幫助我們
push
一個(gè)上下文實(shí)例
def create_app():
app = Flask(__name__)
with app.app_context():
init_db()
return app
需要注意的是
current_app
是“線程”本地變量,所以
current_app
需要在視圖函數(shù)或命令行函數(shù)中使用,否則也會(huì)報(bào)錯(cuò)。
要理解這一點(diǎn)就要對(duì)服務(wù)器程序工作機(jī)制有所了解。一般服務(wù)器程序都是多線程程序,它會(huì)維護(hù)一個(gè)線程池,對(duì)于每個(gè)請(qǐng)求,服務(wù)器會(huì)從線程池中獲取一個(gè)線程用于處理這個(gè)客戶端的請(qǐng)求,而應(yīng)用的
current_app
、
request
等變量是“線程”本地變量,它們是綁定在“線程”中的(相當(dāng)于線程自己獨(dú)立的內(nèi)存空間),所以也在線程環(huán)境下才能夠使用。
在
Flask
中是否也是通過(guò)線程本地變量來(lái)實(shí)現(xiàn)的呢?
這個(gè)問(wèn)題我們?cè)诤竺娴?
工作原理
一節(jié)會(huì)給出答案。
0x01 g
若要在應(yīng)用上下文中存儲(chǔ)數(shù)據(jù),
Flask
提供了
g
這個(gè)變量為我們達(dá)到這個(gè)目的。
g
其實(shí)就是
global
的縮寫,它的生命周期是跟應(yīng)用上下文的生命周期是一樣的。
例如在一次請(qǐng)求中會(huì)多次查詢數(shù)據(jù)庫(kù),可以把這個(gè)數(shù)據(jù)庫(kù)連接實(shí)例保存在當(dāng)次請(qǐng)求的
g
變量中,在應(yīng)用上下文生命周期結(jié)束關(guān)閉連接。
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()
0x02 request
request
封裝了客戶端的
HTTP
請(qǐng)求,它也是一個(gè)線程本地變量。
沒(méi)有把這個(gè)變量放在處理
api
請(qǐng)求的函數(shù)中,而是通過(guò)線程本地變量進(jìn)行封裝,極大地方便使用,以及也使得代碼更加簡(jiǎn)潔。
request
的生命周期是跟
current_app
是一樣的,從請(qǐng)求開(kāi)始時(shí)創(chuàng)建到請(qǐng)求結(jié)束銷毀。同樣地
Flask
在處理請(qǐng)求時(shí)就會(huì)
push
一個(gè)
request
和應(yīng)用上下文的代理實(shí)例,然后才可以使用。如果沒(méi)有
push
就使用就會(huì)報(bào)錯(cuò)
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.
通常這個(gè)錯(cuò)誤在測(cè)試代碼中會(huì)經(jīng)常遇到,如果需要在單元測(cè)試中使用
request
,可以使用
test_client
或者在
with
語(yǔ)句中使用
test_requet_context()
進(jìn)行模擬
def generate_report(year):
format = request.args.get('format')
...
with app.test_request_context(
'/make_report/2017', data={'format': 'short'}):
generate_report()
0x03 session
前面講到如果在一個(gè)請(qǐng)求期間共享數(shù)據(jù),可以使用
g
變量,但如果要在不同的請(qǐng)求(
request
)之間共享數(shù)據(jù),那就需要使用
session
,這是一個(gè)私有存儲(chǔ)的字典類型。可以像操作字典一樣操作
session
。
session
是用戶會(huì)話,可以保存請(qǐng)求之間的數(shù)據(jù)。例如在使用
login
接口進(jìn)行用戶登錄之后,把用戶登錄信息保存在
session
中,然后訪問(wèn)其它接口時(shí)就可以通過(guò)
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)
0x04 工作原理
我們知道
Flask
在處理一個(gè)請(qǐng)求時(shí),
wsgi_app()
這個(gè)方法會(huì)被執(zhí)行。而在
Flask
的源碼內(nèi)部
request
和
current_app
是通過(guò)
_request_ctx_stack
這個(gè)棧結(jié)構(gòu)來(lái)保存的,分別為
# 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)
需要注意
最新的版本源碼會(huì)有些不同
request
和
current_app
分別是有兩個(gè)棧結(jié)構(gòu)來(lái)存儲(chǔ):
_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
通過(guò)
_app_ctx_stack.top
進(jìn)行封裝的。
request
和
session
是
_request_ctx_stack
的封裝。
LocalProxy
是
werkzeug
庫(kù)中
local
對(duì)象的代理。
LocalStack
顧名思義是一個(gè)實(shí)現(xiàn)了棧的數(shù)據(jù)結(jié)構(gòu)。
前面提到全局變量是跟線程綁定的,每個(gè)線程都有一個(gè)獨(dú)立的內(nèi)存空間,在
A
線程設(shè)置的變量,在
B
線程是無(wú)法獲取的,只有在
A
線程中才能獲取到這個(gè)變量。這個(gè)在
Python
的標(biāo)準(zhǔn)庫(kù)有
thread locals
的概念。
然而在
Python
中除了線程外還有進(jìn)程和協(xié)程可以處理并發(fā)程序的技術(shù)。所以為了解決這個(gè)問(wèn)題
Flask
的依賴庫(kù)
werkzeug
就實(shí)現(xiàn)了自己的本地變量
werkzeug.local
。它的工作機(jī)制跟線程本地變量(
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)
方法中就把封裝了請(qǐng)求信息的
request
變量綁定到了local變量中。然后在相同的上下文下例如在一次請(qǐng)求期間,就可以通過(guò)
local.request
來(lái)獲取到這個(gè)請(qǐng)求對(duì)應(yīng)的
request
信息。
同時(shí)還可以看到
LocalManager
這個(gè)類,它是本地變量管理器,它可以確保在請(qǐng)求結(jié)束之后及時(shí)的清理本地變量信息。
在源碼中對(duì)
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 tomanager.locals
. Every time the manager cleans up,
it will clean up all the data left in the locals for this context.
Local
不能自我管理,需要借助
LocalManager
這個(gè)管家來(lái)實(shí)現(xiàn)請(qǐng)求結(jié)束后的清理工作。
0x05 總結(jié)
current_app
、
g
、
request
和
session
是
Flask
中常見(jiàn)4個(gè)全局變量。
current_app
是當(dāng)前
Flask
服務(wù)運(yùn)行的實(shí)例,
g
用于在應(yīng)用上下文期間保存數(shù)據(jù)的變量,
request
封裝了客戶端的請(qǐng)求信息,
session
代表了用戶會(huì)話信息。
0x06 學(xué)習(xí)資料
- https://werkzeug.palletsprojects.com/en/0.15.x/local/
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
