1. 程式人生 > >flask上下文管理

flask上下文管理

stack 鍵值 slots 另一個 light ttr accep nbsp push

flask的上下文管理分應用上下文和請求上下文:

官方文檔裏是說先理解應用上下文比較好,不過我還是覺得反過來,從請求上下文開始記錄比較合適,所以這篇先記錄請求上下文。

那麽問題來了,什麽才是請求上下文:

通俗點說,其實上下文就像一個容器,包含了很多你需要的信息

request和session都屬於請求上下文

request 針對的是http請求作為對象

session針對的是更多是用戶信息作為對象

上下文的結構

說到上下文這個概念的數據結構,這裏需要先知道,他是運用了一個Stack的棧結構,也就說,有棧所擁有的特性,push,top,pop等

  

請求上下文 ----- RequestContext

當一個請求進來的時候,請求上下文環境是如何運作的呢?還是需要來看一下源碼

下面是當wsgi_app被調用的時候,最一開始的動作,這裏的ctxcontext的縮寫

class Flask(_PackageBoundObject):

# 省略一部分代碼

def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) #上下文變量ctx被賦值為request_context(environ)的值
ctx.push() #

再來看下request_context是一個什麽樣的方法,看看源碼

看他的返回值,他返回的其實是RequestContext類生成的一個實例對象,看字面意思就知道是一個請求上下文的實例對象了.

這裏可以註意看下他的函數說明,他舉了一個例子,非常簡單,ctx先push,最後再pop,和用with的方法作用是一毛一樣的

這其實就是一個請求到響應最簡單的骨架,側面反映了request生命周期

class Flask(_PackageBoundObject):

#省略部分代碼

def request_context(self, environ):
"""ctx = app.request_context(environ)
ctx.push()
try:
do_something_with(request)
finally:
ctx.pop()"""
return RequestContext(self, environ)

繼續往下層看,RequestContext是從ctx.py模塊中引入的,所以去找RequestContext的定義

class RequestContext(object):
"""The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the
`_request_ctx_stack` and removed at the end of it. It will create the
URL adapter and request object for the WSGI environment provided.

Do not attempt to use this class directly, instead use
:meth:`~flask.Flask.test_request_context` and
:meth:`~flask.Flask.request_context` to create this object."""

#省略部分說明

def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None

#省略部分代碼

def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)

app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)

if hasattr(sys, ‘exc_clear‘):
sys.exc_clear()

_request_ctx_stack.push(self)

註意一下__init__方法,他的第一個參數是app實例對象,所以在前面額app.py文件內,他的生成方法第一個參數是self,另外,還要傳入environ參數

這樣,回到wsgi_app的函數內部,我們其實已經有了ctx這個變量的值了

所以接下去的一步就是非常重要的ctx.push()

首先會判斷上下文棧的頂端是否有元素,如果是沒元素的,就返回None

如果有元素,會彈出該元素

接著看最後一行,會進行_request_ctx_stack的push動作,參數是self,這裏的self實際上就是上下文實例 ctx,也就是說,把上下文的內容進行壓棧,放到棧頂了。

看到這裏,又引入了一個新的對象 _request_ctx_stack,這其實是一個非常重要的概念,他就是上下文環境的數據結構,也就是結構

繼續找這個對象來自哪裏,發現他來自於同級目錄的globals,打開後發現,原來所有的上下文環境的定義,都在這裏,怪不得名字取成全局變量

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)


def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app


# context locals
_request_ctx_stack = LocalStack() #請求上下文的數據結構
_app_ctx_stack = LocalStack() #引用上下文的數據結構
current_app = LocalProxy(_find_app) #從這個開始的4個,都是全局變量,他們其實通過代理上下文來實現的
request = LocalProxy(partial(_lookup_req_object, ‘request‘))
session = LocalProxy(partial(_lookup_req_object, ‘session‘))
g = LocalProxy(partial(_lookup_app_object, ‘g‘))

上下文的數據結構分析

看到 _request_ctx_stack是LocalStack的實例對象,那就去找LocalStack的源碼了,他來自於werkzeug工具包裏面的local模塊

class LocalStack(object):

def __init__(self):
self._local = Local()

#中間省略部分代碼

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

#中間省略部分代碼

@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

其中最主要的三個方法是,__init__初始化方法, push壓棧方法,以及top元素的訪問方法
__init__初始化方法其實很簡單,他把LocalStack的實例(也就是_request_ctx_stack)的_local屬性,設置為了Local類的實例

所以這裏需要先看一下Local類的定義,他和LocalStack在同一個模塊內

class Local(object):
__slots__ = (‘__storage__‘, ‘__ident_func__‘)

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

def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)

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}

Local類的實例對象,其實就是包含了2個屬性

一個叫 __storage__ 的字典

另一個叫 __ident_func__ 的方法,他這個方法其實是get_ident,這個方法不多說,他是從_thread內置模塊裏面導入的,他的作用是返回線程號

這部分有點繞,因為在Local和LocalStack兩個類裏面來回穿梭

Local類的定義看完以後,回過去看LocalStack的push方法

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

他會先去取 LocalStack實例的_local屬性,也就是Local()實例的stack屬性, 如果沒有這個屬性,則返回None

如果是None的話,則開始建立上下文棧結構,返回值rv代表上下文的整體結構

_local的stack屬性就是一個棧結構
這裏的obj,其實是對應最一開頭的RequestContext裏面的push方法裏的self,也就是,他在push的時候,傳入的對象是上下文RequestContext的實例對象

這裏要再看一下Local類的__setattr__方法了,看看他如何賦值

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

他其實是一個字典嵌套的形式,因為__storage__本身就是一個字典,而name和value又是一組鍵值

註意,value本身也是一個容器,是list

所以,他的內部形式實際上是 __storage__ ={{ident1:{name1:value1}},{ident2:{name2:value2}},{ident3:{name3:value3}}}

他的取值方式__getattr__ 就是__storage__[self.__ident_func__()][name]

這樣每個線程對應的上下文棧都是自己本身,不會搞混。

至此,當一個請求上下文環境被建立完之後,到儲存到棧結構頂端的過程,就完成了。

這個時候,棧頂元素裏面已經包含了大量的信息了,包括像這篇文章裏面最重要的概念的request也包含在裏面了

全局變量request

來看一下request的定義,他其實是棧頂元素的name屬性,經過LocalProxy形成的一個代理

request = LocalProxy(partial(_lookup_req_object, ‘request‘))

以上代碼可以看成是 request = LocalProxy(_request_ctx_stack.top.request) = LocalProxy (_request_ctx_stack._local[stack][-1].request)

也就是棧頂元素內,name叫做request對象的值,而這個值,包含了很多的內容,包括像 HTTP請求頭的信息,都包括在內,可以提供給全局使用

但是,這個request對象,早在RequestContext實例創建的時候,就被建立起來了

def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None

這個是RequestContext類的定義,他的實例有request=app.request_class屬性

實例被壓入上下文棧頂之後,只是通過LocalProxy形成了新的代理後的request,但是內容其實是前面創建的。

所以說,他才能夠使用request這個屬性來進行請求對象的訪問

request來自於Request類

上面的request對象,是通過RequestContext的定義中

request = app.request_class(environ)建立起來的,而request_class = Request類,而Request類則是取自於werkzeuk的 wrappers模塊

這個有空再研究了,主要還是和HTTP請求信息有關系的,比如header parse,ETAG,user Agent之類

class Request(BaseRequest, AcceptMixin, ETagRequestMixin,
UserAgentMixin, AuthorizationMixin,
CommonRequestDescriptorsMixin):

"""Full featured request object implementing the following mixins:

- :class:`AcceptMixin` for accept header parsing
- :class:`ETagRequestMixin` for etag and cache control handling
- :class:`UserAgentMixin` for user agent introspection
- :class:`AuthorizationMixin` for http auth handling
- :class:`CommonRequestDescriptorsMixin` for common headers
"""

所以說,通過RequestContext上下文環境被壓入棧的過程,flask將app和request進行了掛鉤.

LocalProxy到底是一個什麽東西

LocalProxy的源代碼太長了,就不貼了,關鍵看下LocalProxy和Local及LocalProxy之間的關系

Local和LocalStack的__call__方法,都會將實例,轉化成LocalProxy對象

class LocalStack(object):

#省略部分代碼

def __call__(self):
def _lookup():
rv = self.top
if rv is None:
raise RuntimeError(‘object unbound‘)
return rv
return LocalProxy(_lookup)

class Local(object):

#省略部分代碼

def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)

LocalProxy最關鍵的就是一個_get_current_object方法,一個__getattr__的重寫

@implements_bool
class LocalProxy(object):

#省略部分代碼

__slots__ = (‘__local‘, ‘__dict__‘, ‘__name__‘)

def __init__(self, local, name=None):
object.__setattr__(self, ‘_LocalProxy__local‘, local)
object.__setattr__(self, ‘__name__‘, name)

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()
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)

__getattr__方法和 _get_current_object方法聯合一起,返回了真實對象的name屬性,name就是你想要獲取的信息.

這樣,你就可以通過request.name 來進行request內部信息的訪問了。

flask上下文管理