Flask框架從入門到精通之上下文(二十三)

知識點 一、請求上下文 二、應用上下文python

1、概況

Flask從客戶端收到請求時,要讓視圖函數能訪問一些對象,這樣才能處理請求。請求對象是一個很好的例子,它封裝了客戶端發送的HTTP請求。程序員

要想讓視圖函數可以訪問請求對象,一個顯而易見的方式是將其做爲參數傳入視圖函數,不過這會致使程序中的每一個視圖函數都增長一個參數,除了訪問請求對象,若是視圖函數在處理請求時還要訪問其餘對象,狀況會變得更糟。爲了不大量無關緊要的參數把視圖函數弄得一團糟,Flask使用上下文臨時把某些對象變爲全局可訪問。flask

  • request 和 session 都屬於請求上下文對象。
    • request:封裝了HTTP請求的內容,針對的是http請求。舉例:user = request.args.get('user'),獲取的是get請求的參數。
    • session:用來記錄請求會話中的信息,針對的是用戶信息。舉例:session['name'] = user.id,能夠記錄用戶信息。還能夠經過session.get('name')獲取用戶信息。

2、問題

request做爲全局對象就會出現一個問題,咱們都知道後端會開啓不少個線程去同時處理用戶的請求,當多線程去訪問全局對象的時候就會出現資源爭奪的狀況。也會出現用戶A的請求參數被用戶B請求接受到,那怎麼解決每一個線程只處理本身的request呢? 後端

在這裏插入圖片描述
解決這個問題就是用到threading.local對象,稱之爲線程局部變量。用於爲每一個線程開闢一個空間來保存它獨有的值。它的內部是如何實現的呢?解決這個問題能夠考慮使用使用一個字典,key線程的id,val爲對應線程的變量。下面是本身實現Local。

import threading
import time

try:
    from greenlet import getcurrent as get_ident  # 協程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident  # 線程

class Local(object):

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
        # 上面等價於self.__storage__ = {};self.__ident_func__ = get_ident;
        # 可是若是直接賦值的話,會觸發__setattr__形成無限遞歸

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

local_values = Local()

def func(num):
    local_values.name = num
    time.sleep(0.1)
    print(local_values.name,threading.current_thread().name)

for i in range(1,20):
    t = threading.Thread(target=func,args=(i,))
    t.start()

print(local_values.__storage__)
複製代碼

3、請求上下文

1、請求到來時:bash

  • 將初始的初始request封裝RequestContext對象ctx
def request_context(self, environ):
        return RequestContext(self, environ)
複製代碼
  • 藉助LocalStack對象將ctx放到Local對象中
_request_ctx_stack = LocalStack()
_request_ctx_stack.push(self)
複製代碼

2、執行視圖時:session

  • 導入from flask import request,其中request對象是LocalProxy類的實例化對象
request = LocalProxy(partial(_lookup_req_object, 'request'))
複製代碼
  • 當咱們取request的值時,request.mehod -->執行LocalProxy的__getattr__
def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)
複製代碼
  • 當咱們設置request的值時request.header = 'xxx' -->執行LocalProxy的__setattr__
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
複製代碼

說道底,這些方法的內部都是調用_lookup_req_object函數:去local中將ctx獲取到,再去獲取其中的method或header 3、請求結束:多線程

  • ctx.auto_pop,鏈式調用LocalStack的pop,將ctx從Local中pop
def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9 Added the `exc` argument. """
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)

                # If this interpreter supports clearing the exception information
                # we do that now. This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()

            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc)

            assert rv is self, 'Popped wrong request context. ' \
                '(%r instead of %r)' % (rv, self)
複製代碼

4、 應用上下文

from flask import g:在一次請求週期裏,用於存儲的變量,便於程序員傳遞變量的時候使用。app

四個全局變量原理都是同樣的ide

# globals.py
_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'))
複製代碼

當咱們request.xxx和session.xxx的時候,會從_request_ctx_stack._local取對應的值函數

當咱們current_app.xxx和g.xxx的時候,會從_app_ctx_stack._local取對應的值

  • current_app就是flask的應用實例對象
  • g變量是解決咱們在一次請求過程傳遞參數的問題,咱們能夠往g變量塞不少屬性,在同一次請求中,能夠從另外一個函數中取出。固然也能夠用函數參數這種方法解決,g變量適用於參數多的狀況。
from flask import Flask, request, g

app = Flask(__name__)


@app.route('/')
def index():
    name = request.args.get('name')
    g.name = name
    g.age = 12
    get_g()
    return name


# 在一塊兒請求中,能夠用g變量傳遞參數
def get_g():
    print(g.name)
    print(g.age)


if __name__ == '__main__':
    # 0.0.0.0表明任何能表明這臺機器的地址均可以訪問
    app.run(host='0.0.0.0', port=5000)  # 運行程序

複製代碼

5、多線程下的請求

當程序開始運行,而且請求沒到來的時候,就已經生成了兩個空的Local,即:

_request_ctx_stack = LocalStack()  ->LocalStack類中的__init__定義了Local對象
_app_ctx_stack = LocalStack()
複製代碼

當同時有線程處理請求的時候,兩個上下文對應的Local對象變成以下:

_request_ctx_stack._local = {
    線程id1:{‘stack’;[ctx1]},  # 只放一個爲何用list,實際上是模擬棧
    線程id1:{‘stack’;[ctx2]},
   ... 
}

_app_ctx_stack._local = {
    線程id1:{‘stack’;[app_ctx1]},
    線程id1:{‘stack’;[app_ctx2]},
   ... 
}
複製代碼

歡迎關注個人公衆號:

image
相關文章
相關標籤/搜索