Flask基础学习-上下文篇

这一篇我们将深入学习flask的上下文机制。

1. Working outside of application context引发的思考

我们抒写一段测试代码,进行debug调试。

image-20180805213925830

我们实例化了一个Flask核心对象,从current_app读取的却是一个本地代理LocalProxy。当尝试从current_app中读取数据的时候就会触发RuntimeError: Working outside of application context

我们从图中看到current_app并不是指向flask的核心对象。

我们看下current_app的源码:

我们看到current_apprequest都是LocalProxy类。这引发了一系列的思考:

什么是LocalProxycurrent_app和核心对象的关系,requestRequest吗?

让我们带着疑问进行下个小节学习吧ヾ(◍°∇°◍)ノ゙

2. AppContext、RequestContext、Flask和Request之间的关系

在flask中有两个上下文,一个是应用上下文,一个是请求上下文。我们把这两个上下文当做对象来思考。这两个对象就是对flask和request对象的一些封装。比如说应用上下文是对核心对象Flask对象的封装,请求上下文是对Request对象的封装,然后在此基础上封装一些好用的方法。

我们看下关于AppContextRequestContext的源码:

image-20180805220301033

我们在实例化方法中看到将flask核心对象存储到了AppContext的属性中,并且实现了左侧红框四个方法。

image-20180805220631561

我们在实例化方法中看到将Request对象存储到了RequestContext的属性中(右侧红线框),并且实现了左侧红框四个方法。

flask核心对象Flask存储在AppContext中,Request对象存储在RequestContext中。

我们对这四个对象的作用和意义做下解释:

  1. Flask是flask的核心对象,里面保存了配置信息,提供了注册路由和视图函数的一系列功能。
  2. AppContext是对Flask进行了封装,并增加了一些额外的参数。
  3. Request保存了一些请求信息,如:请求参数和完整的url等。
  4. RequestContext是对Request进行了封装,并增加了一些额外的参数。

我们在编码的时候真正想使用的是FlaskRequest,但是并不是直接导入,我们应该从AppContextRequestContext获得这两个对象。

如何获得呢?这就是我们接下来要学习的LocalProxy,通过它提供的间接操作上下文的能力,直接获得想要的对象。

看下源码:

1
2
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))

上面的current_apprequest分别指向FlaskRequest,通过设计模式中的本地代理模式来从上下文中获得。

3. 详解flask上下文与出入栈

这一小节是flask核心知识点,学会了是面试装逼利器。我们看下老师总结的一张图片。

image-20180806113023589

我们从一个web请求进入到flask框架来探讨下核心机制。

当一个请求进入到flask框架后,flask首先会实例化一个请求上下文RequestContext,这个请求上下文封装了此次请求的一些信息(保存在Request中),在实例化完成之后会将上下文使用push方法推到一个栈_request_ctx_stack中。在flask中使用LocalStack来表示栈,_request_ctx_stack就是LocalStack的实例化对象。

注意:在实例化RequestContext对象推入到栈_request_ctx_stack之前会先进行一次检查,检查栈_app_ctx_stack的栈顶是否为空或者是否为当前对象,如果不是将实例化一个AppContext对象,并将其推入到栈_app_ctx_stack当中,然后再将RequestContext的实例化对象推入到栈_request_ctx_stack中,一定要注意这个逻辑。

我们再看下current_apprequest这两个变量。

这两个变量永远指向对应的栈的栈顶。因此当我们使用这两个变量的时候实际上就是操作这两个栈顶对用的上下文。当栈顶对应的上下文存在的时候就能正常使用,当栈顶不存在上下文的时候就会报文章开头的Working outside of application contextunbound状态。

下面我们看看一开始的代码,想下如何解决unbound

我们知道之所以出现unbound是因为栈顶为空,我们可以自己推上下文到栈中。

我们可以使用flask提供的app_context方法获得上下文,我么看下源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def app_context(self):
"""Create an :class:`~flask.ctx.AppContext`. Use as a ``with``
block to push the context, which will make :data:`current_app`
point at this application.

An application context is automatically pushed by
:meth:`RequestContext.push() <flask.ctx.RequestContext.push>`
when handling a request, and when running a CLI command. Use
this to manually create a context outside of these situations.

::

with app.app_context():
init_db()

See :doc:`/appcontext`.

.. versionadded:: 0.9
"""
return AppContext(self)

我们从函数的文档注释可以看到我们所学习的知识点。

image-20180806135631420

我们获得上下文之后手动推到栈顶,这样就能获得current_app

之前我们说过current_app指向的是对应程序上下文的栈顶,但是current_app这个代理具体返回的是什么呢?我们来看下源码:

1
2
3
4
5
6
7
8
9
10
11
def _find_app():
# 取栈顶元素
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
# 返回app对象
return top.app


_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)

我们看到current_app代理最终返回的是flask的核心对象app

同样看下request的源码:

1
2
3
4
5
6
7
8
9
10
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)


# context locals
_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, 'request'))

最终返回也不是请求上下文,而是request对象。

完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, current_app, request, Request


app = Flask(__name__)

ctx = app.app_context()
# 入栈
ctx.push()

a = current_app

d = current_app.config['DEBUG']
# 出栈
ctx.pop()

对于单元测试和离线应用的时候,使用手动推栈很有必要。

4. flask上下文与with语句

我们之前看AppContext源码的时候看到了实现了上下文协议的两个魔法方法__enter____exit__。因此我们可以使用with语句简化上面的代码。

1
2
3
4
5
6
7
8
9
from flask import Flask, current_app, request, Request


app = Flask(__name__)

with app.app_context():
a = current_app

d = current_app.config['DEBUG']

关于with语句的一些知识:

实现了上下文协议的对象才能使用with

实现了上下文协议的对象被称为上下文管理器

只要一个对象实现了 __enter__ __exit__魔法方法就是实现了上下文协议

with后面的语句被称为上下文表达式,上下文表达式必须返回一个上下文管理器

上下文表达式必须返回一个上下文管理器,那下面的 as 后面的 f代表什么呢?

1
2
with open(r'') ad f:
print(f.read())

as后面的变量是指魔法方法__enter__的返回值

我们写段代码验证下:

image-20180806155959433

我们看到的确是__enter__函数的返回值,当什么都不返回的时候,将会为None。

5. 上下文管理器中的exit方法

image-20180806214319724

我们看到__exit__参数多了几个,分别是代表异常类型,异常值,和异常栈。

__exit__也是有返回值的,分别是 TrueFalse。当返回True的时候在with语句不再抛出异常,当返回False的时候外面会继续抛出异常。如果我们在with外面用try…except捕捉,将继续捕捉到错误。当我们什么都不返回就是代表返回None,即False。

知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!