本文首發微信公衆號:戰渣渣
歡迎大家關注。
關聯知識
WEB開發——Python WSGI協議詳解
Flask進擊篇(1)——Flask運行流程
背景
在Flask中可直接導入from flask import request, current_app, g並直接使用,那Flask是如何保證這個request對象就是這個請求對象呢?
Flask官方中有提到使用的是本地線程對象,這篇文章就來揭示其原理
Flask線程間上下文安全
Falsk完成線程安全的原理,是在啓動之後進程裏維護request棧和app棧,棧是通過線程ID來保證每個請求的線程安全。
實現主要依賴三個類Local,LocalStack和LocalProxy,下面看一下具體的實現原理
三個類構建本地數據
1. Local
先看Local的源碼,實質並不是Flask中定義的,而是Flask依賴的werkzeug庫所定義。
# werkzeug\local.py
# get_ident獲取線程和協程的唯一標識
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
# 實際的Local類
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
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}
可以看到其定義的兩個屬性__storage__, __ident_func__以及三個方法__getattr__,__setattr__,__release_local__。
-
屬性__storage__是多層級字典,第一層key是隱含的線程ID或者協程ID,第二層的key是實際使用的關鍵字
-
屬性__ident_func__可以看到是get_ident函數,get_ident函數要麼是通過thread庫獲取當前執行單元的線程ID,要麼是通過greenlet庫獲取當前執行協程的協程ID。
另外可以看到Local這個類的三個方法,實質是通過重寫Python內置函數__setattr__和__getattr__來實現線程或者協程間數據隔離
-
獲取local某屬性時
如:local.age實質觸發的是__getattr__方法
-
先獲取到當前線程ID——__ident_func__函數獲取,然後在__storage__字典中找到線程ident對應的結果集
-
從獲取到的結果中再查找age屬性
-
-
設置local某屬性時
如:local.age = 12 實際觸發的是__setattr__方法
先獲取到當前線程ID——__ident_func__函數獲取,然後在__storage__字典設置相應的屬性字典集
另一個__release_local__方法就是將相應的線程數據刪除。
畫個簡圖比較起來更直觀一些。
主線程中生成一個對象local=Local(),三個線程中進行相同的操作local.no=每個線程對應的數。爲每個線程都開闢一個存儲,所以誰來取或者存就找到自己對應中的位置,雖然取得key都一樣,但是每次存取都是隻關於自己的值。
2. LocalStack
LocalStack也是定義在Flask所依賴的werkzeug庫,從字面意思來理解,它就是Local的堆棧操作,看一下源碼如何定義。
class LocalStack(object):
def __init__(self):
self._local = Local()
def __release_local__(self):
self._local.__release_local__()
def _get__ident_func__(self):
return self._local.__ident_func__
def _set__ident_func__(self, value):
object.__setattr__(self._local, "__ident_func__", value)
__ident_func__ = property(_get__ident_func__, _set__ident_func__)
del _get__ident_func__, _set__ident_func__
def __call__(self):
def _lookup():
rv = self.top
if rv is None:
raise RuntimeError("object unbound")
return rv
return LocalProxy(_lookup)
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, "stack", None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv
def pop(self):
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
LocalStack實質就是圍繞着Local來進行操作,根據上面我們讀完Local的源碼可以看到,
- LocalStack定義了一個Local對象
- 給這個對象設置了一個stack屬性,且這個屬性是一個列表
- LocalStack中定義了對這個列表進行壓棧,出棧等方法
- 給類中的Local對象提供了自定義ident_func的方法
3. LocalProxy
LocalProxy字面意思就是做一個Local的代理,我們先從一個request的定義來看LocalProxy的用法,然後結合源碼來看LocalProxy到底是用來做什麼?
# venv/Lib/site-packages/werkzeug/local.py
@implements_bool
class LocalProxy(object):
__slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
def __init__(self, local, name=None):
object.__setattr__(self, "_LocalProxy__local", local)
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 _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)
def __getattr__(self, name):
if name == "__members__":
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
類中稍微有些難理解的就是關於object.__setattr__(self, “_LocalProxy__local”, local)的作用,實際就是給self設置一個__local屬性。這是Python類中關於私有變量的定義。可以看Python的官方定義python私有變量。
可以看到這個類將所有Python類所內置的方法都進行重寫,重寫後所有的操作都是基於類中所定義的_get_current_object方法返回的對象進行操作。
而這個方法中返回值就是初始化時所給定的local對象執行返回的結果。如果創建時指定的不是Local對象,則直接執行此方法。如果給定的是Local對象,則根據類名查找對應的對象。
現在這個比較抽象,這個代理到底是做的什麼? 我們結合Flask定義全局的request對象來看。假如我們想獲取請求的方法是什麼,那我們使用的就是request.method。
下面是request定義的源碼
# flask/globals.py
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
request = LocalProxy(partial(_lookup_req_object, "request"))
- 根據LocalProxy的源碼中重寫的__getattr__方法,先執行_get_current_object方法獲取到對象,然後再獲取返回對象method屬性。
- 創建LocalProxy時傳遞的函數是_lookup_req_object的偏函數,實際就是_lookup_req_object且name=request
- 再LocalProxy中__local就是一個函數,所以在執行_get_current_object就是執行_lookup_req_object且name=request返回的值,然後再取其method屬性
- 此時再執行_lookup_req_object函數,從_request_ctx_stack獲取top的request
使用Proxy可以簡單快捷的使用request.method獲取相應的值,其核心就是每次獲取時都會執行對應的函數,而函數中每次返回的值都是線程安全。保證數據正確且優雅。 否則我們每次都去執行一個函數來獲取其值,然後再取其屬性。