Flask 上下文

上下文
  • 上下文是一种属性的有序序列,为驻留在环境内的对象定义的环境。在对象的激活过程中创建上下文,对象被配置为要求某些自动服务。
  • 场景:同步,事务,实时激活,安全性等。
  • 例:在计算机中,相对于进程而言,上下文就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。可以理解为上下文是一个环境的一个快照,是一个用来保存状态的对象。在程序中我们写的函数大都不是单独完整的,在使用一个函数完成自身功能的时候,很可能需要同其他的部分进行交互,需要其他外部环境变量来支持,上下文就是给外部环境的变量的赋值,使函数能够正确的运行。
Python中上下文的定义
  • 上下文管理器
  • 任何实现了 enter() 和 exit() 方法的对象都可称之为上下文管理器,上下文管理器对象可以使用 with 关键字。显然,文件(file)对象也实现了上下文管理器。
  • 自定义上下文管理器
class File():
    # File()时调用,相当于创建类对象执行__init__属性
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename, self.mode)
        # 返回as 后面的值
        return self.f

    def __exit__(self, *args):
        print("will exit")
        # 退出关闭,释放资源
        self.f.close()
with File('out.txt', 'w') as f:
    print("writing")
    f.write('hello, python')

enter() 方法返回资源对象,这里就是你将要打开的那个文件对象,exit() 方法处理一些清除工作。

因为 File 类实现了上下文管理器,现在就可以使用 with 语句了。
这样,你就无需显示地调用 close 方法了,由系统自动去调用,哪怕中间遇到异常 close 方法也会被调用。

  • SO :
    Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。
from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f
    f.close()
#调用

with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")

Python 提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 enter 方法中执行,yield 之后的语句在 exit 方法中执行。紧跟在 yield 后面的值是函数的返回值。

Flask两种上下文
  • 1. 应用上下文(application context)
    1.1 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等
    1.2 它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。

    • 应用上下文对象有:current_app,g
  • 2. 请求上下文(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')获取用户信息。

Flask中文文档: This is an Flask中文文档

请求上下文

from flask import request
@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return '<p>Your browser is %s</p>' % user_agent

Flask中有四种请求hook,分别是@before_first_request @before_request @after_request @teardown_request

  • 是Python提供的语法糖,可以为提供上下文环境省略简化一部分工作。这里就简化了其压栈和出栈操作,请求线程创建时,Flask会创建应用上下文对象,并将其压入flask._app_ctx_stack**的栈中,然后在线程退出前将其从栈里弹出。
    应用上下文也提供了装饰器来修饰hook函数
from flask import Flask
from flask import json
from werkzeug.routing import BaseConverter  # 进入源代码结构查看routing模块的结构

app = Flask(__name__)


@app.route('/')
def hello_world():
    print('代码正在执行')
    dic = {
        'name': 'ithema'
    }
    return json.dumps(dic)


# 在第一次请求之前会调用
# 应用地方:程序的初始化操作(一些准备工作,数据的链接请求)
@app.before_first_request
def before_first_request():
    print('before_first_request')


# 在每一次请求之前会调用
# 应用:用户的权限验证
@app.before_request
def before_request():
    #  在这里可以做一些判断,这里使用return 后面的都不会执行(视图函数都不会进入)
    print('before_request')
    # if ....
    # return 'hehe'


# 在每次请求执行之后执行
@app.after_request
def after_request(response):
    print('after_request')
    # 这里可以对响应对象做处理
    # 对所有返回的json数据进行处理(所有的都处理掉)
    response.headers["Content-Type"] = "application/json"
    return response


# 在每次请求执行的最后执行,如果服务器出现错误,这里也可以获取
@app.teardown_request
def teardown_request(error):
    # 可以记录错误
    # 以后可能会用到请求数据库的自动提交
    print('teardown_request:%s' % error)


if __name__ == '__main__':
    app.run(debug=True)

如同上面的代码一样,在每个请求上下文的函数中我们都可以访问request对象,然而request对象却并不是全局的,因为当我们随便声明一个函数的时候,比如:

def handle_request():
    print 'handle request'
    print request.url 
if __name__=='__main__':
    handle_request()
# 错误报警
RuntimeError: working outside of request context。

因此可知,Flask的request对象只有在其上下文的生命周期内才有效,离开了请求的生命周期,其上下文环境不存在了,也就无法获取request对象了。而上面所说的四种请求hook函数,会挂载在生命周期的不同阶段,因此在其内部都可以访问request对象。

  • 对于Flask Web应用来说,每个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突,这就需要使用到Thread Local。
    Thread Local
    对象是保存状态的地方,在Python中,一个对象的状态都被保存在对象携带的一个字典中, Thread Local 则是一种特殊的对象,它的“状态”对线程隔离 —— 也就是说每个线程对一个 Thread Local 对象的修改都不会影响其他线程。这种对象的实现原理也非常简单,只要以线程的 ID 来保存多份状态字典即可,就像按照门牌号隔开的一格一格的信箱。
    在Python中获取Thread Local最简单的方式是threading.local()
>>> import threading
>>> storage = threading.local()
>>> storage.foo = 1
>>> print(storage.foo)
1
>>> class AnotherThread(threading.Thread):
...         def run(self):
...             storage.foo = 2
...             print(storage.foo) # 这这个线程里已经修改了
>>>
>>> another = AnotherThread()
>>> another.start()
2
>>> print(storage.foo) # 但是在主线程里并没有修改
1

因此只要有Thread Local对象,就能让同一个对象在多个线程下做到状态隔离。

Flask是一个基于WerkZeug实现的框架,因此Flask的App Context和Request Context是基于WerkZeug的Local Stack的实现。这两种上下文对象类定义在flask.ctx中,ctx.push会将当前的上下文对象压栈压入flask._request_ctx_stack中,这个_request_ctx_stack同样也是个Thread Local对象,也就是在每个线程中都不一样,上下文压入栈后,再次请求的时候都是通过_request_ctx_stack.top在栈的顶端取,所取到的永远是属于自己线程的对象,这样不同线程之间的上下文就做到了隔离。请求结束后,线程退出,ThreadLocal本地变量也随即销毁,然后调用ctx.pop()弹出上下文对象并回收内存。

应用上下文

current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数
  • 加载了哪些配置文件,导入了哪些配置
  • 连了哪个数据库
  • 有哪些public的工具类、常量
  • 应用跑再哪个机器上,IP多少,内存多大
current_app.name
current_app.test_value='value'

g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

g.name='abc'
注意:不同的请求,会有不同的全局变量

推荐阅读更多精彩内容