4.flask核心机制

一. flask中的经典错误

我们现在工程中新建test/test1.py:

from flask import Flask, current_app

app = Flask(__name__)

a = current_app   # 如果调试, 这里会出现unbund未绑定
d = current_app.config['DEBUG']

运行test/test1.py,出现如下报错,: RuntimeError: Working outside of application context.

1.以前使用flask的request也出现过类似错误
2.我们在app/spider/yushu_book.py中使用过` current_app.config['PER_PAGE']`,为什么没有报错?

我们查看current_app的源码:

current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

可以看到current_app, request, session, g都使用了LocalProxy, 这也是产生错误的原因所在。


二. AppContext、RequestContext、 Flask与Request之间的关系

AppContext应用上下文,其实是对Flask以及Flask外部一些其他内容的封装。
RequestCont请求上下文,同样的是对Request以及Request外部内容的封装

我们编码时,真正要使用的是Flask或Request对象,
但使用时必须从AppContext,RequestContext两个上下文中获取。
而LocalProxy代理,提供了访问Flask和Request的能力

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

flask运行的流程图:


1.请求进入时, 检测_app_ctx_stack栈中有没有app, 没有的话push推入
2.Request会被push推入_request_ctx_stack栈中
3.使用flask.current_app时,会从_app_ctx_stack.top得到app对象
4.使用flask.request时, 会从_request_ctx_stack.top得到Request对象

回过头来再看一次flask.current的源代码:

# context locals
_request_ctx_stack = LocalStack()    # 存放Request的栈
_app_ctx_stack = LocalStack()       # 存放app的栈
current_app = LocalProxy(_find_app)     # 栈中取出app
request = LocalProxy(partial(_lookup_req_object, 'request'))   # 栈中取出Request
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

其中current_app = LocalProxy(_find_app)的_find_app, 就是从栈中取出app:

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

request = LocalProxy(partial(_lookup_req_object, 'request'))的_lookup_req_object,就是栈中取出request:

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

在我们的视图函数中使用current_app(@app.route做装饰器的函数中), 不会报错, 因为有请求进入的之后, flask会自动确保栈中有app。

而test1.py中, 我们就得手动推入,才不会报错。 修改test/test1.py:

from flask import Flask, current_app

app = Flask(__name__)

ctx = app.app_context()   # 获取封装了appcontext上下文
ctx.push()                  # push入栈中
a = current_app
d = current_app.config['DEBUG']
ctx.pop()                   # 结束记得pop出栈

我们往往在离线应用和单元测试中, 会手动push和pop


四. flask上下文与with语句

test/test1.py可以使用上下文管理器with改写:

扫描二维码关注公众号,回复: 1644164 查看本文章
with app.app_context():
    a = current_app
    d = current_app.config['DEBUG']

效果是一样的。 这里涉及with上下文管理器的原理:

1. 对实现了上下文协议的对象,使用with语句
2. 对实现了上下文协议的对象称为   上下文管理器
3. __enter__  __exit__即实现了上下文协议

我们阅读app_context()的源码:

    def app_context(self):
        """
        ···
        """
        return AppContext(self)

AppContext()的源码:

class AppContext(object):
    """
    ···
    """

    def __init__(self, app):···
     

    def push(self):···
       

    def pop(self, exc=_sentinel):···
       

    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):   # __exit__ 会除self外,再传3个参数
        self.pop(exc_value)

        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
            reraise(exc_type, exc_value, tb)

首先 app.app_context()函数返回了AppContent, 而AppContent这个类,有__enter__和__exit__方法, 并在其中进行了push和pop。

另外with的注意点:

使应with ....   as  XXXX:  时,这里的 XXXX是__enter__的return对象

自定义一段代码

class A:
    def __enter__(self):
        a = 1

    def __exit__(self):
        b = 2


with A() as obj_A:
    pass

进行调试过程中, 发现obj_A 为None。
对自定义代码修改:

class A:
    def __enter__(self):
        a = 1
        return a

    def __exit__(self):
        b = 2


with A() as obj_A:
    pass

这回进行调试时, 发现obj_A是a.
但不会进入__exit__, 并且在运行结束时, 自定义代码会报错!


五. 详解上下文管理器的__exit__方法

上面自定义代码运行出错的原因,其实是__exit__传入的参数不对。__exit__除self外必须传入三个参数exc_type, exc_value, tb
修改代码:

class MyResource:
    def __enter__(self):
        print('connect to resource')
        return self

    def __exit__(self,  exc_type, exc_value, tb):
        print('close resource connection')
        b = 2

    def query(self):
        print('query data')


with MyResource() as resource:
    resource.query()

调试发现可以进入__exit__并且, 运行完后,不会报错.

那么问题来了,exc_type, exc_value, tb这三个最为必需品的参数又是干什么的呢?
这三个参数其实是做异常处理的, 修改代码:

class MyResource:
    def __enter__(self):
        print('connect to resource')
        return self

    def __exit__(self,  exc_type, exc_value, tb):
        print('close resource connection')
        b = 2

    def query(self):
        print('query data')


with MyResource() as resource:
    1/0    # 故意出错
    resource.query()

在__exit__中print('close resource connection')这行打上断点, 可以看到这三参数:

exc_type    {type}<class 'ZeroDivision' >      异常类型
exc_value   division by zero                  异常原因描述
tb          traceback Object at 0x1113f06c8   异常堆栈

所以我们可以在__exit__中, 通过查看tb参数来判断是否出现异常, 例子:

class MyResource:
    def __enter__(self):
        print('connect to resource')
        return self

    def __exit__(self,  exc_type, exc_value, tb):
        if tb:      # tb有值, 表明出现异常
            print('process execption')
        else:
            print('no exception')
        print('close resource connection')

    def query(self):
        print('query data')


with MyResource() as resource:
    1/0
    resource.query()

对__exit__的返回值补充说明:

如果__exit__返回了True,             则__exit__顺利退出, 不会向外抛异常。
如果__exit__返回False或无return,     则__exit__结束后会向外抛出异常。

猜你喜欢

转载自blog.csdn.net/weixin_41207499/article/details/80721410