亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Python Web Flask源碼解讀(四)——全局變量

系統(tǒng) 2060 0

關(guān)于我
一個有思想的程序猿,終身學(xué)習(xí)實(shí)踐者,目前在一個創(chuàng)業(yè)團(tuán)隊(duì)任team lead,技術(shù)棧涉及Android、Python、Java和Go,這個也是我們團(tuán)隊(duì)的主要技術(shù)棧。
Github:https://github.com/hylinux1024
微信公眾號:終身開發(fā)者(angrycode)

Flask 中全局變量有 current_app request g session 。不過需要注意的是雖然標(biāo)題是寫著全局變量,但實(shí)際上這些變量都跟當(dāng)前請求的上下文環(huán)境有關(guān),下面一起來看看。

current_app 是當(dāng)前激活程序的應(yīng)用實(shí)例; request 是請求對象,封裝了客戶端發(fā)出的 HTTP 請求中的內(nèi)容; g 是處理請求時用作臨時存儲的對象,每次請求都會重設(shè)這個變量; session 是用戶會話,用于存儲請求之間需要保存的值,它是一個字典。

0x00 current_app

應(yīng)用程序上下文可用于跟蹤一個請求過程中的應(yīng)用程序?qū)嵗?梢韵袷褂萌肿兞恳粯又苯訉?dǎo)入就可以使用 (注意這個變量并不是全局變量)
Flask 實(shí)例有許多屬性,例如 config 可以 Flask 進(jìn)行配置。

一般在創(chuàng)建 Flask 實(shí)例時

          
            from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
...
          
        

通常不會直接導(dǎo)入 app 這個變量,而是使用通過導(dǎo)入 current_app 這個應(yīng)用上下文實(shí)例代理

          
            from flask import current_app
          
        
current_app 的生命周期

Flask 應(yīng)用在處理客戶端請求( request )時,會在當(dāng)前處理請求的線程中推送( push )一個上下文實(shí)例和請求實(shí)例( request ),請求結(jié)束時就會彈出( pop )請求實(shí)例和上下文實(shí)例,所以 current_app request 是具有相同的生命周期的,且是綁定在當(dāng)前處理請求的線程上的。

如果一個沒有推送上下文實(shí)例就直接使用 current_app ,會報(bào)錯

          
            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 )應(yīng)用上下文實(shí)例,從上面的錯誤信息可以知道,可以使用 with 語句,幫助我們 push 一個上下文實(shí)例

          
            def create_app():
    app = Flask(__name__)

    with app.app_context():
        init_db()

    return app
          
        

需要注意的是 current_app 是“線程”本地變量,所以 current_app 需要在視圖函數(shù)或命令行函數(shù)中使用,否則也會報(bào)錯。

要理解這一點(diǎn)就要對服務(wù)器程序工作機(jī)制有所了解。一般服務(wù)器程序都是多線程程序,它會維護(hù)一個線程池,對于每個請求,服務(wù)器會從線程池中獲取一個線程用于處理這個客戶端的請求,而應(yīng)用的 current_app request 等變量是“線程”本地變量,它們是綁定在“線程”中的(相當(dāng)于線程自己獨(dú)立的內(nèi)存空間),所以也在線程環(huán)境下才能夠使用。

Flask 中是否也是通過線程本地變量來實(shí)現(xiàn)的呢? 這個問題我們在后面的 工作原理 一節(jié)會給出答案。

0x01 g

若要在應(yīng)用上下文中存儲數(shù)據(jù), Flask 提供了 g 這個變量為我們達(dá)到這個目的。 g 其實(shí)就是 global 的縮寫,它的生命周期是跟應(yīng)用上下文的生命周期是一樣的。

例如在一次請求中會多次查詢數(shù)據(jù)庫,可以把這個數(shù)據(jù)庫連接實(shí)例保存在當(dā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 請求,它也是一個線程本地變量。

沒有把這個變量放在處理 api 請求的函數(shù)中,而是通過線程本地變量進(jìn)行封裝,極大地方便使用,以及也使得代碼更加簡潔。

request 的生命周期是跟 current_app 是一樣的,從請求開始時創(chuàng)建到請求結(jié)束銷毀。同樣地 Flask 在處理請求時就會 push 一個 request 和應(yīng)用上下文的代理實(shí)例,然后才可以使用。如果沒有 push 就使用就會報(bào)錯

          
            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.
          
        

通常這個錯誤在測試代碼中會經(jīng)常遇到,如果需要在單元測試中使用 request ,可以使用 test_client 或者在 with 語句中使用 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

前面講到如果在一個請求期間共享數(shù)據(jù),可以使用 g 變量,但如果要在不同的請求( request )之間共享數(shù)據(jù),那就需要使用 session ,這是一個私有存儲的字典類型。可以像操作字典一樣操作 session
session 是用戶會話,可以保存請求之間的數(shù)據(jù)。例如在使用 login 接口進(jìn)行用戶登錄之后,把用戶登錄信息保存在 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)
          
        

0x04 工作原理

我們知道 Flask 在處理一個請求時, wsgi_app() 這個方法會被執(zhí)行。而在 Flask 的源碼內(nèi)部 request current_app 是通過 _request_ctx_stack 這個棧結(jié)構(gòu)來保存的,分別為

          
            # 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 分別是有兩個棧結(jié)構(gòu)來存儲: _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 進(jìn)行封裝的。 request session _request_ctx_stack 的封裝。 LocalProxy werkzeug 庫中 local 對象的代理。 LocalStack 顧名思義是一個實(shí)現(xiàn)了棧的數(shù)據(jù)結(jié)構(gòu)。
前面提到全局變量是跟線程綁定的,每個線程都有一個獨(dú)立的內(nèi)存空間,在 A 線程設(shè)置的變量,在 B 線程是無法獲取的,只有在 A 線程中才能獲取到這個變量。這個在 Python 的標(biāo)準(zhǔn)庫有 thread locals 的概念。
然而在 Python 中除了線程外還有進(jìn)程和協(xié)程可以處理并發(fā)程序的技術(shù)。所以為了解決這個問題 Flask 的依賴庫 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) 方法中就把封裝了請求信息的 request 變量綁定到了local變量中。然后在相同的上下文下例如在一次請求期間,就可以通過 local.request 來獲取到這個請求對應(yīng)的 request 信息。

同時還可以看到 LocalManager 這個類,它是本地變量管理器,它可以確保在請求結(jié)束之后及時的清理本地變量信息。

在源碼中對 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 這個管家來實(shí)現(xiàn)請求結(jié)束后的清理工作。

0x05 總結(jié)

current_app g request session Flask 中常見4個全局變量。 current_app 是當(dāng)前 Flask 服務(wù)運(yùn)行的實(shí)例, g 用于在應(yīng)用上下文期間保存數(shù)據(jù)的變量, request 封裝了客戶端的請求信息, session 代表了用戶會話信息。

0x06 學(xué)習(xí)資料

  • https://werkzeug.palletsprojects.com/en/0.15.x/local/

更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 欧美一级毛片免费观看视频 | 亚洲日本视频 | 一区二区三区在线免费观看视频 | 久草久 | 久久免费在线观看 | 偷偷狠狠的日日高清完整视频 | 女人18特级一级毛片免费视频 | 欧美啪啪毛片一区二区 | 色日韩在线| 99re这里有免费视频精品 | 亚洲国产系列久久精品99人人 | 亚洲综合无码一区二区 | 男女一级免费视频 | 四虎影视永久费观看在线 | 精品视频在线一区 | 精品国产一区二区三区四区色 | 91视频国产91久久久 | 国产看色免费 | 国产成人啪午夜精品网站 | 一区二区视频免费看 | 奇米777在线 | 老子不卡影院 | 中国一级特黄高清免费的大片 | 久久青草免费免费91线频观看 | 四虎在线影视在线影库 | 手机看片福利在线 | 久热视线观看免费视频 | 玖玖在线精品 | 超碰在线小说 | 亚洲成a人一区二区三区 | 激情婷婷成人亚洲综合 | 久久手机在线视频 | 久久99精品久久久久子伦 | 国产一区二区三区高清 | 九九热在线视频免费观看 | 99久久精品国产综合一区 | 色图一区 | 国产高清在线观看麻豆 | 国产大陆亚洲精品国产 | 精品国产hd | 午夜免费一级片 |