Flask -- (16)Flask中的上下文及实现原理

上下文:相当于一个容器,保存了Flask程序运行过程中的一些信息。
在计算机中,相对于进程而言,上下文就是进程执行时的环境。具体就是各个
Flask中有两种上下文:请求上下文 应用上下文

请求上下文(request context)

request 和 session 都属于请求上下文对象。
request:封装了HTTP请求的内容,针对的是http请求。

user = request.args.get('user')

session:用来记录请求会话中的信息,针对的是用户信息。

session['name'] = user.id
session.get('name')

举例请求上下文:

    from flask import request,Flask
    app = Flask(__name__)

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

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

代码运行结果类似于:

    Your browser is Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36

Flask的request对象只有在上下文的生命周期内才有效,离开了请求的声明周期,其上下文环境就不存在了,也就无法获取request对象了。

可以使用Flask内部的方法request_contest()来构建一个请求上下文

    from flask import Flask
    from flask import request
    from werkzeug.test import EnvironBuilder

    app = Flask(__name__)
    ctx = app.request_context(EnvironBuilder("/",'http://localhost/').get_environ())
    ctx.push()
    try:
        print request.url
    finally:
        ctx.pop()

对于Flask Web应用来说,每一个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突,这就需要用到Thread Local

Thread Local

对象是保存状态的地方,在Python中,一个对象的状态都被保存在对象携带的一个字典上。Thread Local是一种特殊的对象,它的状态对线程隔离——也就是说每个线程对一个Thread Local对象的修改都不会影响其他的线程。

此类对象的实现原理:以线程的ID来保存多份状态字典。
在Python中获取Thread Local最简单的方法是使用threading.local()

    # coding:utf-8
    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) # 在这个线程中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()弹出上下文对象并回收内存。

应用上下文 (application context)

current_app和g都属于应用上下文
current_app:表示当前运行程序文件的程序实例,可以通过current_app.name打印出当前应用程序实例的名字。
g:处理请求时,用于临时存储的对象,每次请求都会重置这个变量。比如:我们可以获取一些临时请求的用户信息。

1.当调用app = Flask(name) 的时候,创建了程序应用对象app
2.request 在每次http请求发生时,WSGI server调用Flask.call();然后在Flask的内部创建request对象;
3.app的生命周期大于request和g,一个app存活期间,可能发生多次http请求,所以会有多个request和g。
4.最终传入视图函数,通过return、redirect和render_template生成response对象,返回客户端。

从一个Flask App读入配置并且启动开始,就进入了App Context,在其中我们可以配置文件,打开资源文件,通过路由规则反向构造URL。
举栗子:

    from flask import Flask,current_app
    app = Flask(__name__)

    @app.route("/index")
    def index():
        return "hello %s" %current_app.name

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

current_app是一个本地代理,它的类型是werkzeug.local. LocalProxy,它所代理的即是我们的app对象,也就是说current_app == LocalProxy(app)。使用current_app是因为它也是一个ThreadLocal变量,对它的改动不会影响到其他线程。可以通过current_app._get_current_object()方法来获取app对象。current_app只能在请求线程里存在,因此它的生命周期也是在应用上下文里,离开了应用上下文也就无法使用。

这里写图片描述

和请求上下文一样,可以手动创建应用上下文:

    from flask import Flask
    from flask import current_app

    app = Flask(__name__)
    with app.app_context():
        print current_app.name

这里的with语句和with open() as f 一样,是Python提供的语法糖,可以为提供上下文环境省略简化一部分工作。这里就简化了其压栈和出栈操作,请求线程创建时,Flask会创建应用上下文对象,并将其压入flask._app_ctx_stack的栈中,然后在线程退出前将其从栈里弹出。
应用上下文也提供了装饰器来修饰hook函数,@teardown_request,它会在上下文生命周期结束前,也就是_app_ctc_stack出栈前被调用。

需要注意的问题

当 app = Flask(name)构造出一个 Flask App 时,App Context 并不会被自动推入 Stack 中。所以此时 Local Stack 的栈顶是空的,current_app也是 unbound 状态。
这里写图片描述

在编写离线脚本的时候,如果直接在一个 Flask-SQLAlchemy 写成的 Model 上调用 User.query.get(user_id),就会遇到 RuntimeError。因为此时 App Context 还没被推入栈中,而 Flask-SQLAlchemy 需要数据库连接信息时就会去取 current_app.configcurrent_app 指向的却是 _app_ctx_stack为空的栈顶。
解决的办法是运行脚本正文之前,先将 App 的 App Context 推入栈中,栈顶不为空后 current_app这个 Local Proxy 对象就自然能将“取 config 属性” 的动作转发到当前 App 上。
这里写图片描述

那么为什么在应用运行时不需要手动 app_context().push()呢?因为 Flask App 在作为 WSGI Application 运行时,会在每个请求进入的时候将请求上下文推入 _request_ctx_stack中,而请求上下文一定是 App 上下文之中,所以推入部分的逻辑有这样一条:如果发现 _app_ctx_stack为空,则隐式地推入一个 App 上下文。

请求上下文和应用上下文的区别

请求上下文:保存了客户端和服务器交互的数据。
应用上下文:在flask程序运行的过程中,保存的一些配置信息,比如程序文件名,数据库的连接,用户信息等。

思考

1. 既然在 Web 应用运行时里,应用上下文 和 请求上下文 都是 Thread Local 的,那么为什么还要独立二者?
是为了能让两个以上的Flask应用共存在一个WSGI应用中,这样在请求中,需要通过应用上下文来获取当前请求的应用信息。

2. 既然在Web应用运行时中,一个线程同时只处理一个请求,那么 _req_ctx_stack和 _app_ctx_stack肯定都是只有一个栈顶元素的。那么为什么还要用“栈”这种结构?
需要考虑在非Web Runtime的环境中使用的时候,在多个App的时候,无论有多少个App,只要主动去Push它的app context,context stack就会累积起来,这样,栈顶永远是当前操作的 App Context。当一个 App Context 结束的时候,相应的栈顶元素也随之出栈。如果在执行过程中抛出了异常,对应的 App Context 中注册的 teardown函数被传入带有异常信息的参数。
这么一来就解释了这两个问题,在这种单线程运行环境中,只有栈结构才能保存多个 Context 并在其中定位出哪个才是“当前”。而离线脚本只需要 App 关联的上下文,不需要构造出请求,所以 App Context 也应该和 Request Context 分离。

3. App和Request是怎么关联起来的?
(1)Flask实现请求上下文:

    # 代码摘选自flask 0.5 中的ctx.py文件,
    class _RequestContext(object):
        def __init__(self, app, environ):
            self.app = app 
            self.request = app.request_class(environ) 
            self.session = app.open_session(self.request) 
            self.g = _RequestGlobals()

(2)Flask中使用_RequestContext的方法如下:

class Flask(object): 
    def request_context(self, environ): 
        return _RequestContext(self, environ)

在Flask类中,每次请求都会调用这个request_context函数。这个函数则会创建一个_RequestContext对象,该对象需要接收WerkZeug中的environ对象作为参数。这个对象在创建时,会把Flask实例本身作为实参传进去,所以虽然每次http请求都创建一个_RequestContext对象,但是每次创建的时候传入的都是同一个Flask对象,因此:

由同一个Flask对象相应请求创建的_RequestContext对象的app成员变量都共享一个application

通过Flask对象中创建_RequestContext对象,并将Flask自身作为参数传入的方式实现了多个request context对应一个application context。

然后可以看self.request = app.request_class(environ)这句
由于app成员变量是app = Flask(name) 这个对象,所以app.request_class就是Flask.request_class,而在Flask类的定义中:

request_class = Request
    class Request(RequestBase):
        ....

所以self.request = app.request_class(environ)实际上是创建了一个Request对象。由于一个http请求对应一个_RequestContext对象的创建,而每个_RequestContext对象的创建对应一个Request对象的创建,所以,每个http请求对应一个Request对象。

因此:

  • application 就是指app = Flask(name)对象
  • request 就是对应每次http 请求创建的Request对象
  • Flask通过_RequestContext将App与Request关联起来

转载自

作者:馒头白啊白
链接:简书链接

更新于 2018.8.28

总述

什么是上下文上下文相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
Flask 中有两种上下文,请求上下文和应用上下文。

请求上下文(request context)

Flask从客户端收到请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象是一个很好的例子,它封装了客户端发送的HTTP请求。

要想让视图函数能够访问请求对象,一个显而易见的方式是将其作为参数传入视图函数,不过这会导致程序中的每个视图函数都增加一个参数,除了访问请求对象,如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。为了避免大量可有可无的参数把视图函数弄得一团糟,Flask使用上下文临时把某些对象变为全局可访问。

request 和 session 都属于请求上下文对象。
- request:封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get(‘user’),获取的是get请求的参数。
- session:用来记录请求会话中的信息,针对的是用户信息。举例:session[‘name’] = user.id,可以记录用户信息。还可以通过session.get(‘name’)获取用户信息。

当调用app = Flask(name)的时候,创建了程序应用对象app;
request 在每次http请求发生时,WSGI server调Flask.call();然后在Flask内部创建的request对象;
app的生命周期大于request,一个app存活期间,可能发生多次http请求,所以就会有多个request。
最终传入视图函数,通过return、redirect或render_template生成response对象,返回给客户端。

应用上下文 (application context)

它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。

应用上下文对象有:current_app,g

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'

两者区别

请求上下文:保存了客户端和服务器交互的数据

应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如说程序名,数据库连接,应用信息等。

实现原理可参考:
https://segmentfault.com/a/1190000004223296

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值