flask上下文管理

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内部信息的访问了。

原文地址:https://www.cnblogs.com/Amery313/p/8660712.html

时间: 2024-10-10 09:43:42

flask上下文管理的相关文章

Flask上下文管理、session原理和全局g对象

一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能,这三个数中,其中一个数必须是3 # 我们就可以使用偏函数来帮着我们传参 from functools import partial # partial:给add这个函数固定传一个数字 3 new_add = partial(add, 3) # 因此新的函数只需要传2个参数 new_add(1,1)

Flask 上下文管理-- (session,request,current_app的传递)

Flask session,request,current_app的传递 1 flask的 request, session 和 current_app 都是 设置方式比较新颖 -- 通过上下文管理的方式实现的 每次请求进来app.run调用 call 方法, 创建 一个本地线程(唯一标识作为键) -- 然后把实例化的对象push到一个地方,在请求结束后返回的时候 pop 掉 local = { '标识':{'stack':[RequestContext(),]} } 2 补充 partial

python - Flask 上下文管理 流程

上下文管理:    - 请求上下文 (ctx=RequestContext())  : request/session    - App上下文  (app_ctx=AppContext())  : app/g 1. 请求进来执行 __call__ 方法.2. __call__ 方法调用 wsgi_app.3. wsgi_app:    - (1)实例化两个方法:    ctx = RequestContext(request,session)    app_ctx = AppContext(ap

深度剖析Flask上下文管理机制

前言 上下文这个概念多见于文章中,是一句话中的语境,也就是语言环境.一句莫名其妙的话出现会让人不理解什么意思,如果有语言环境的说明,则会更好,这就是语境对语意的影响. 上下文是一种属性的有序序列,为驻留在环境内的对象定义环境.在对象的激活过程中创建上下文,对象被配置为要求某些自动服务,如同步.事务.实时激活.安全性等等. 如在计算机中,相对于进程而言,上下文就是进程执行时的环境.具体来说就是各个变量和数据,包括所有的寄存器变量.进程打开的文件.内存信息等.可以理解上下文是环境的一个快照,是一个用

利用flask上下文管理原理,实现线程之间的数据安全

由于一般的程序基本上都是IO密集型,如果只是为了保证数据的安全而去开启进程,这必定是浪费资源的行为:为了保证数据的安全,借用flask的上下文的原理,如下代码实现 from threading import Threadimport time try: from greenlet import getcurrent as get_ident # 协程的唯一标识except ImportError: try: from thread import get_ident except ImportEr

flask上下文管理之threading.local

Flask之上下文管理 知识储备之问题情境: request中的参数: 单进程单线程 单进程多线程-->reqeust 会因为多个请求,数据发生错乱.--->可以基于threading.local对象 单进程单线程(多协程)threading.local对象做不到(因为一个线程下多个协程同享一个线程的资源) 解决办法: ? 自定义类似threading.local对象(支持协程)---保证多协程下数据的安全 先来看一下下面这段代码(支持多线程): # -*- coding: utf-8 -*-

Flask 上下文管理

flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递. flask是如何做的呢? 1.Python 实现的本地线程 保证即使是多个线程,自己的值也是互相隔离. import threading local_values = threading.local() def func(num): local_values.name = num import time time.sleep(1) print(local_values.name, threa

flask上下文管理相关 - threading.local 以及原理剖析

threading.local 面向对象相关: setattr/getattr class Foo(object): pass obj = Foo() obj.x1 = 123 # object.__setattr__(obj,'x1',123) print(obj.x1) # object.__getattr__(obj,'x1') Local类的模拟 简易版 class Local(object): def __init__(self): # self.storage = {} object

Flask 之 上下文管理

Flask上下文管理 分类: 请求上下文管理 应用上下文管理 请求上下文管理 request a. 温大爷:wsig b. 赵毅: ctx = ReuqestContext(session,request) ctx.push() c. 刘松:LocalStack,把ctx对象添加到local中 d. 空调:Local __storage__={ 1321:{stack:[ctx,]} } session a. 温大爷:wsig b. 赵毅: ctx = ReuqestContext(sessio