Flask:上下文管理

1. werkzurg

from werkzur.serving import run_simple

def run(environ,start_response):
    reuturn [b'hello world']
    
if __name__ == "__main__":
    run_simple('localhost',4000,run)    # run_simple  --> 啓動監聽接收 socket(一個死循環); run 會加上 () 去執行

2. 全部請求的入口

def __call__(self, environ, start_response):        # 當請求來的時候,纔會執行 __call__ 方法
    """The WSGI server calls the Flask application object as the
    WSGI application. This calls :meth:`wsgi_app` which can be
    wrapped to applying middleware."""
    return self.wsgi_app(environ, start_response)        # wsgi_app()  真正去執行源碼
    
# app.run()  這行代碼執行的時候,並無執行 __call__ 方法

3. Local()  ---> 把不一樣線程的數據隔離

import time
import threading

# 獲取 線程或者協程的惟一id
try:
    import greenlet
    get_ident = greenlet.getcurrent
except Exception:
    get_ident = threading.get_ident
    
    
class Local(object):
    DIC = {}    # 數據都放到這個大字典中
    
    def __getattr__(self,item):
        get_ident = get_ident()
        if get_ident in self.DIC:
            return self.DIC[get_ident].get(item)
            
        return None
        
        
    def __setattr__(self,key,value):
        get_ident = get_ident()
        
        if get_ident in self.DIC:
            self.DIC[get_ident][key] = value
            
        else:
            self.DIC[get_ident] = {key:value}
        
    
# __getattr__ 和  __setattr__ 的用處  ; 同一個對象的同一個屬性,根據線程和協程號這個惟一標識,來獲取不一樣的值 

4. 上下文管理(第一次)

請求到來時:
    # ctx = RequestContext(self,environ)  # self 是 app對象,environ表示請求相關的原始數據
    # ctx.request = Request(environ)  
    # ctx.session = None
    
    # 將包含了 request和session 的ctx對象打包放到某個地方(至關於一個大字典;根據線程或協程加個惟一標識,放進去的,因此數據相互隔離)
        {
            1232:{ctx:ctx對象},
            ...
        }
視圖函數:
    from flask import request,session
    # 上述代碼背後的過程:根據當前線程或者協程的惟一標識,取到 ctx對象,而後取到 request和session
請求結束:
    根據當前線程的惟一標識,將 大字典 中該線程對應的數據移除

5. 偏函數

import functools

def func(a,b):
    return a+b
    
new_func = functools.partial(func,6)    # 第一個參數是函數名;第二個參數 6 會看成 func 的第一個參數自動傳入 func 中

ret = new_func(2)        # new_func(2) 執行時, 就是 func 函數在執行,6做爲第一個參數自動傳入 func(),2做爲第二個參數傳入func
print(ret)

# 偏函數 ---> 幫助開發人員自動傳遞參數

6. 基於列表維護一個棧:

class Stack(object):
    """
    基於列表維護一個棧
    """
    def __init__(self):
        self.stack = []

    def push(self,item):
        self.stack.append(item)

    def pop(self):
        return self.stack.pop()

    def top(self):
        """
        讀取最後一個值
        """
        return self.stack[-1]

7. flask 中的  Local 類 和 LocalStack 類

# 本身寫的 Local:
try:
    from greenlet import getcurrent as get_ident
except Exception as e:
    from threading import get_ident

class Local(object):
    """docstring for Local"""
    def __init__(self, arg):
        object.__setattr__(self,"storage",{})        # Local 實例化的時候會在對象中存一個 "storage" 的空字典;注意這種設置對象屬性值點語法的方法
        # 這是不能使用 self.storage = {}  ,由於 self.storage 會調用下面的 __setattr__ ,__setattr__ 中也有 self.storage

    def __setattr__(self,key,value):
        get_ident = get_ident()        # 線程號

        if get_ident not in self.storage:
            self.storage[get_ident] = {key,value}
        else:
            self.storage[get_ident][key] = value


    def __getattr__(self,item):
        get_ident = get_ident()

        if get_ident in self.storage:
            return self.storage[get_ident].get(item)

        return None


# 實現的效果: 一個對象, .同一個屬性的時候 可獲取到不一樣的值(根據當前線程號等惟一標識)---> 爲每一個線程開闢一塊獨立的空間


# flask 中的 Local 和 LocalStack 類:

# flask 源碼中的 Local 部分源碼:
# 查詢「路徑」: flask 中的 globals  ---> LocalStack  ---> Local 

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):
    # 類中的 __slots__() 的做用:只容許該類的實例添加 __slots__ () 中的屬性
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    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)


obj = Local()
obj.stack = []        
"""
此時 obj. 的時候, 並非給 obj 對象新添加了一個屬性,而是把 "stack" 做爲 key,[] 做爲 value 放到了 __storage__ 這個大字典中,以下:
{
    "線程號":{"stack":[]}
}
"""
obj.stack.append("abc")
obj.stack.append("123")
obj.stack.pop()
# 經過重寫 __setattr__ 方法,Local對象全部經過 . 方法設置屬性值時,都把 屬性名和其對應的值放到了 __storage__ 這個大字典當前線程號對應的字典中;
# 經過重寫 __getattr__ 方法,Local對象經過 . 方法獲取屬性值時,也是從 __storage__ 這個大字典中 當前線程號對應的字典中 獲取該屬性key 對應的值

"""
LocalStack 存的數據格式:
__storage__ = {
    1231:{"stack":[]},
    ...
}
"""

# 每次 obj.stack 這個棧 append 或者 pop 時都要加上 .stack ,下面作簡化: 用一個代理 LocalStack

class LocalStack(object):
    def __init__(self):
        self._local = Local()        # 實例化 Local ---> 獲得一個 __storage__ 屬性對應的空字典 :  Local()對象.__storage__ = {}

    def push(self,item):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)        # self._local.stack  ---> 觸發 Local 的 __getattr__ 方法

        if rv is None:
            self._local.stack = rv = []            # self._local.stack 和 rv 同時指向同一個引用; 此處執行 __setattr__ 方法
        rv.append(item)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            return stack[-1]        # 讀取棧中的最後一個值
        else:
            return stack.pop()

ls = LocalStack()
ls.push("abc")
ls.push("123")
ls.pop()

"""
__storage__ = {
    "線程號":{"stack":[]}
}
"""

# LocalStack 對象至關於一個代理,幫助咱們在Local中維護一個列表,並把這個列表維護成一個棧


"""
小結:
Local 做用: 爲每一個線程開闢一個獨立的空間 (內部維護了一個 __storage__ 的大字典,該字典中的key是當前線程號,字典的value是該線程獨立的數據)
LocalStack 做用: 幫助咱們把 Local 中的一個列表維護成一個棧
"""

 8. 以上綜合應用

# LocalStack的對象 ls push() 時再也不傳字符串,而是傳一個字典,如: ls.push({"request":"xxx","session":"yyy"})
# 全局變量只有在程序啓動時會加載執行

上下文管理:flask

1. request 上下文管理  (請求上下文管理)session

app.__call__  --->  app.wsgi_app  
wsgi_app 中 :
    1. ctx = self.request_context(environ) ---> 封裝請求數據; environ --> 原始的請求數據
        ctx = RequestContext(self, environ)
        ctx.request = Request(environ)
        ctx.session = None

    2. ctx.push()
        ---> _request_ctx_stack.push(self)        
            1.    # self 便是 ctx 對象, ctx 又封裝了 request 和 session
                # self = ctx = (request,session)

            2. _request_ctx_stack = LocalStack()    ---> 這行代碼執行完後(主要是 LocalStack()),
                                                         會在內存中建立一個名字爲 __storage__ 的空字典
                
                ---> _request_ctx_stack 幫助咱們維護一個 __storage__ = {}
        
        ---> ctx.push() 執行完後 ---> __storage__ = {
                                                        "當前線程號":{"stack":[封裝了 request 和 session 的ctx對象]}
                                                    }

wsgi(幫助咱們初步處理請求)  ---> 而後執行 __call__ 方法  ---> __call__ 方法 執行 wsgi_app 方法
---> wsgi_app 方法中: 
        1. 建立 ctx 對象 :  ctx = RequestContext(self, environ)        # environ 中有 request 和 session
        2. 執行 push 方法      ctx.push()     ---> LocalStack , 把 ctx對象添加到 Local 對象中,
                                                            ---> Local 中的數據格式:
                                                                    __storage__ = {
                                                                        "當前線程號":{"stack":[封裝了request和session的ctx對象]}
                                                                    }

# 走到視圖函數 取數據 :
# 視圖函數取數據也是經過 LocalStack 去 Local 中取數據(ctx對象),而不能直接去 Local 中取
原理: ctx = _request_ctx_stack.top
       ctx.request  # 取 request
       ctx.session  # 取 session
       
    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None
        
"""
第一階段:請求到來
    將 request 和 session 相關數據封裝到 ctx=RequestContext() 對象中。
    再經過 LocalStack 將 ctx 對象添加到 Local 中
    __storage__ = {
        1231:{"stack":[ctx(request,session)]}
    }
第二階段: 視圖函數中獲取 request 或 session
    方式一: 直接找 LocalStack 獲取
        _request_ctx_stack.top.request
    方式二: 經過代理 LocalProxy 獲取
        from flask import request,session
        
上面兩種方式都是經過 LocalStack 去 Local 中取值
"""
        
# 經過代理 LocalProxy 獲取 request 或 session
request = LocalProxy(partial(_lookup_req_object, 'request'))    ---> 獲取 ctx 中的 request
session = LocalProxy(partial(_lookup_req_object, 'session'))    ---> 獲取 ctx 中的 session

# request 是一個 LocalProxy 的對象,以 request.method 爲例, .mothod 會執行 LocalProxy 的 __getattr__ 方法:
@implements_bool
class LocalProxy(object):
    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)        # __local 是 LocalProxy 在實例化的時候傳入的參數
        object.__setattr__(self, '__name__', name)
        if callable(local) and not hasattr(local, '__release_local__'):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, '__wrapped__', local)
            
            
    def __getattr__(self, name):        
        if name == '__members__':
            return dir(self._get_current_object())
            
        # 以 request.method 爲例, 此時 name == "method"
        return getattr(self._get_current_object(), name)
        # 上面的 return 等同於下面的兩句代碼:
        """
        obj = self._get_current_object()    --> 從ctx中獲取 request
        return getattr(obj,name)            --> 從 request 中獲取 method
        """


    def _get_current_object(self):
    """Return the current object.  This is useful if you want the real
    object behind the proxy at a time for performance reasons or because
    you want to pass the object into a different context.
    """
    if not hasattr(self.__local, '__release_local__'):
        return self.__local()            # 執行偏函數
        # __local 是 LocalProxy 在實例化的時候傳入的參數,即偏函數 partial(_lookup_req_object, 'request')--> 獲取到ctx中的request
    try:
        return getattr(self.__local, self.__name__)
    except AttributeError:
        raise RuntimeError('no object bound to %s' % self.__name__)


def _lookup_req_object(name):        # name 爲 "request" 或者 " session"
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)
相關文章
相關標籤/搜索