1-9-flask框架-框架-上下文管理
前言
在了解flask上下文管理机制之前,先来一波必知必会的知识点。
面向对象双下方法
首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__、__getattr__系列、__getitem__系列。
call
这个方法相信大家并不陌生,在单例模式中,我们可能用到过,除此之外,还想就没有在什么特殊场景中用到了。我们往往忽视了它一个很特殊的用法:对象object+()或者类Foo()+()这种很特殊的用法。在Flask上下文管理中,入口就是使用了这种方式。
__getitem__系列
使用这个系列的方法时,我们最大的印象就是调用对象的属性可以像字典取值一样使用中括号([])。使用中括号对对象中的属性进行取值、赋值或者删除时,会自动触发对应的__getitem__、setitem、__delitem__方法。
__getattr__系列
使用对象取值、赋值或者删除时,会默认的调用对应的__getattr__、setattr、__delattr__方法。
对象取值时,取值的顺序为:先从__getattribute__中找,第二步从对象的属性中找,第三步从当前类中找,第四步从父类中找,第五步从__getattr__中找,如果没有,直接抛出异常。
threading.local
在多线程中,同一个进程中的多个线程是共享一个内存地址的,多个线程操作数据时,就会造成数据的不安全,所以我们要加锁。但是,每个线程想拥有一些属于自己的变量,怎么办?
方法一:可以通过维护一个全局字典来实现,字典的key为线程ID,value就存线程自己的变量。
方法二:就是threading.local
threading.local在多线程操作时,会为每一个线程创建一个只属于属于它自己的内存空间,使得线程之间各自操作自己的数据,互相隔离。
import time
import threading
local = threading.local()
def func(n):
local.val = n
time.sleep(5)
print(n)
for i in range(10):
t = threading.Thread(target=func,args=(i,))
t.start()
结果输出 0--9
偏函数
partial是functools下面的一个很有用的小功能,作用:固定函数中的一些参数,返回一个新的函数,方便调用。
Flask中的Local
在Flask中也参照threading.local原理 实现了一个Local类,为什么Flask不直接用threading.local呢?
因为Flask要支持到协程级别的内存隔离。
flask的上下文管理机制
flask 提供两种上下文:
一个是应用上下文(app),application context
另一个是请求上下文(request), request context 。
application context 又演化出来两个变量 current_app 和 g,
而 request context 则演化出来 request 和 session。
- request: 请求上下文的对象,一般用来保存请求的变量,如:args、form等。
- session:请求上下文的对象,一般用来保存会话信息。
- current_app :应用上下文对象,返回当前的app。
- g:应用上下文对象,处理请求时用来作临时存储的对象。
应用上下文current_app和请求上下文request 都是一个全局变量,所有请求都是共享的。
flask有特殊的机制,可以保证每次请求的数据都是隔离的。所以可以直接导入request对象,也不会被一些脏数据影响,并且不需要在每个函数中使用request的时候,都导入request对象。
LocalStack 和 LocalProxy
这里的实现用到了两个东西:LocalStack 和 LocalProxy。它们两个的结果就是我们可以动态地获取两个上下文的内容,在并发程序中每个视图函数都会看到属于自己的上下文,而不会出现混乱。
LocalStack 和 LocalProxy 都是 werkzeug 提供的,定义在 local.py 文件中。在分析这两个类之前,我们先介绍这个文件另外一个基础的类 Local。Local 就是实现了类似 threading.local 的效果——多线程或者多协程情况下全局变量的隔离效果。
理解了 Local,我们继续回来看另外两个类。
LocalStack 是基于 Local 实现的栈结构。如果说 Local 提供了多线程或者多协程隔离的属性访问,那么 LocalStack 就提供了隔离的栈访问。下面是它的实现代码,可以看到它提供了 push、pop 和 top 方法。
上下文的实现:
Flask的上下文管理咱们能够理解为一个生命周期
也就是请求进来到请求出去一共作了哪些事情
请求进来会为每一个请求在Local中创建一个独立空间
每次有请求过来的时候,flask 会先创建当前线程或者进程需要处理的两个重要上下文对象,把它们保存到隔离的栈里面,这样视图函数进行处理的时候就能直接从栈上获取这些信息。
1、请求上下文(request context)
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在flask中,可以直接在视图函数中使用request这个对象进行获取相关数据,而request就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
request
封装了HTTP请求的内容,针对的是http请求。举例:user=request.args.get('user'),获取的是get请求参数
session
用来记录请求会话的信息,针对的是用户信息。举例:session['name']=user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息
2、应用上下文(application context)
字面上是应用上下文,但它不是一直存在的,它只是request context中的一个对app的代理,所谓的local proxy。
应用上下文的作用主要是帮助request获取当前的应用。
应用上下文对象有:current_app,g
1)current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如
应用的启动脚本是哪个文件,启动时指定了哪些参数
加载了哪些配置文件,导入了哪些配置
连了哪些数据库
在哪些public的工具类、常量
应用跑在哪个机器上,IP多少,内存多大
作用:current_app 就是当前运行的flask app,在代码不方便直接操作flask的对象时,可以操作current_app就等价于操作flask app对象
2)g对象
g作为flask程序全局的一个临时变量,充当中间媒介的作用,我们可以通过它在一个请求调用的多个函数间传递一些数据。每次请求都会重设这个变量
from flask import Flask, g
app = Flask(__name__)
def db_query():
user_id = g.user_id
user_name = g.user_name
print(f'user_id={user_id} user_name={user_name}')
@app.route('/')
def get_user_profile():
g.user_id = 123
g.user_name = 'wzs'
db_query()
return 'you are great!'
if __name__ == '__main__':
app.run()