flask核心机制——Flask中的上下文

flask核心机制——Flask中的上下文

八月 01, 2018

【AppContext、RequestContext、Flask与Request之间的关系】

Flask框架有两个上下文。分别为应用级别的上下文请求级别的上下文。它们本质上都是对象。在flask源码的ctx.py中,有两个类AppContext和RequestContext,在这两个类中同样存在四个函数:pop()、push()、__enter__()、__exit()__。Flask核心对象是被存储在AppContext中的,Request对象是被存储在RequestContext中的。

Flask核心对象封装的是Flask框架核心功能,而AppContext是把核心对象做了一个封装,并且附加了一些附加参数。

而Request封装了请求信息。而RequestContext是对Request的一个封装。

【flask的上下文和出入栈】

Flask主要是用来编写web应用的。我们如果要探讨flask是如何操作上下文的,那么就必须从一个请求发起开始探讨:当一个请求进入flask框架之后,首先会实例化一个请求上下文(RequestContext),请求上下文封装了请求的信息(Request),在生成请求上下文之后,会把这个ReqeustContext推入到一个栈(LocalStack)中。_request_ctx_statck 变量用来存储这个栈。当一个请求进来的时候,flask会使用RequestContext的push()方法入栈,把这次请求相关的信息存入到flask的LocalStatck中。在RequestContext入栈之前,flask会首先检查另外一个栈_app_ctx_stack的栈顶的元素,如果为空或者不是当前的对象,那么flask会把一个AppContext推入到_app_ctx_satck中,然后才会执行RequestContext向_reqeust_ctx_stack常量的入栈。

· current_app和request变量是什么?

current_app(Local Proxy)和request(Local Proxy)永远都是指向对应栈的栈顶。所以在使这两个代理的时候,就是在间接操作这两个栈的栈顶的元素,就是两个上下文。如果栈顶是空的,就会出现LocalLocal Proxy unbound的表示(如下图)

【解决LocalProxy unbound的方法】

解决LocalProxy unbound 的方法就是把应用上下文手动入栈。那么如何在代码里把应用上下文推入到栈中?代码如下:

# 入栈方法1
ctx = app.app_context() # 得到AppContext对象
ctx.push() # 完成入栈
a = current_app
d = current_app.config[‘DEBUG’] # 得到DEBUG参数
ctx.pop()

current_app返回的是核心对象app,而不是应用上下文appContext

相同request返回的也是Request对象,而不是requestContext对象。可以在源代码中看出。

疑问:为什么在flask项目代码上使用current_app,不用手动推入上下文却不会报错?

原因:上面已经说明flask的上线文流程,由于项目代码是web代码,代码是在一个请求中,在RequestContext入栈之前。flask会首先检查另外一个栈_app_ctx_stack的栈顶的元素,如果为空或者不是当前的对象,那么flask会把一个AppContext推入到_app_ctx_satck中。所以这个时候使用current_app不会报错。

手动推入的意义:自己编写离线应用或者单元测试的时候需要手动push到栈中。

【with语句的相关基本概念】

1、使用with的条件:可以对一个实现了上下文协议的对象使用with语句

2、一个实现了上下文协议的对象,称为上下文管理器

3、一个对象实现了 __enter__ 和 __exit__ 这两个方法,就是实现了上下文协议

4、上下文表达式(如 app.app_context() )必须要返回一个上下文管理器(如下图所示)。

# 入栈方法2,使用with,更优雅
with app.app_context():
a = current_app
d = current_app.config[‘DEBUG’]

【with结构的实际应用】

# 例子1、数据库资源
__enter__ # 在__enter__方法中 连接数据库
__exit__ # 在__exit__方法中 释放资源,处理异常

# 例子2 文件读写
# 传统写法
try:
f = open(r’filename’)
print(f.read())
finally:
f.close()

# with语句写法
# 上下文管理器的__exit__方法实现了f.close()
with open(r’filename’) as f:
print(f.read())

# 注意:as后面的变量f不是上下文管理器,而是__enter__方法所返回的值

【关于__exit__方法的返回】

__exit__方法的返回只有两种(True or False)
如果返回True,表示在__exit__方法内部已经处理异常,请Python在类的外部不要再抛出异常了。
如果返回False,表示外部会接收异常。如果什么都没返回,那么就是None,也就是等于返回False。下面是示例代码。

# __exit__处理示例代码
class MyResource:

def \_\_enter\_\_(self):
    print('connect to resource')
    return self # 返回MyResource

# \_\_exit\_\_必须有下面四个参数
def \_\_exit\_\_(self, exc\_type, exc\_val, exc_tb):
    if exc_tb:
        print('process exception')
    else:
        print('no exception')
    print('close resource connection')  
    return True

@staticmethod
def query():
    print('query data')

try:
with MyResource() as resource:
1 / 0 # 测试错误
resource.query()
except Exception as e:
print(e)

console:
connect to resource
process exception
close resource connection