2.4 Flask上下文-Flask Web开发实战-万书网
首页

2.4 Flask上下文

关灯 护眼    字体:

上一章 目录 下一章

    我们可以把编程中的上下文理解为当前环境(environment)的快照(snapshot)。如果把一个Flask程序比作一条可怜的生活在鱼缸里的鱼的话,那么它当然离不开身边的环境。

    提示

    这里的上下文和阅读文章时的上下文基本相同。如果在某篇文章里单独抽出一句话来看,我们可能会觉得摸不着头脑,只有联系上下文后我们才能正确理解文章。

    Flask中有两种上下文,程序上下文(application context)和请求上下文(request context)。如果鱼想要存活,水是必不可少的元素。对于Flask程序来说,程序上下文就是我们的水。水里包含了各种浮游生物以及微生物,正如程序上下文中存储了程序运行所必须的信息;要想健康地活下去,鱼还离不开阳光。射进鱼缸的阳光就像是我们的程序接收的请求。当客户端发来请求时,请求上下文就登场了。请求上下文里包含了请求的各种信息,比如请求的URL,请求的HTTP方法等。

    2.4.1 上下文全局变量

    每一个视图函数都需要上下文信息,在前面我们学习过Flask将请求报文封装在request对象中。按照一般的思路,如果我们要在视图函数中使用它,就得把它作为参数传入视图函数,就像我们接收URL变量一样。但是这样一来就会导致大量的重复,而且增加了视图函数的复杂度。

    在前面的示例中,我们并没有传递这个参数,而是直接从Flask导入一个全局的request对象,然后在视图函数里直接调用request的属性获取数据。你一定好奇,我们在全局导入时request只是一个普通的Python对象,为什么在处理请求时,视图函数里的request就会自动包含对应请求的数据?这是因为Flask会在每个请求产生后自动激活当前请求的上下文,激活请求上下文后,request被临时设为全局可访问。而当每个请求结束后,Flask就销毁对应的请求上下文。

    我们在前面说request是全局对象,但这里的“全局”并不是实际意义上的全局。我们可以把这些变量理解为动态的全局变量。

    在多线程服务器中,在同一时间可能会有多个请求在处理。假设有三个客户端同时向服务器发送请求,这时每个请求都有各自不同的请求报文,所以请求对象也必然是不同的。因此,请求对象只在各自的线程内是全局的。Flask通过本地线程(thread local)技术将请求对象在特定的线程和请求中全局可访问。具体内容和应用我们会在后面进行详细介绍。

    为了方便获取这两种上下文环境中存储的信息,Flask提供了四个上下文全局变量,如表2-12所示。

    提示

    这四个变量都是代理对象(proxy),即指向真实对象的代理。一般情况下,我们不需要太关注其中的区别。在某些特定的情况下,如果你需要获取原始对象,可以对代理对象调用_get_current_object()方法获取被代理的真实对象。

    表2-12 Flask中的上下文变量

    我们在前面对session和request都了解得差不多了,这里简单介绍一下current_app和g。

    你在这里也许会疑惑,既然有了程序实例app对象,为什么还需要current_app变量。在不同的视图函数中,request对象都表示和视图函数对应的请求,也就是当前请求(current request)。而程序也会有多个程序实例的情况,为了能获取对应的程序实例,而不是固定的某一个程序实例,我们就需要使用current_app变量,后面会详细介绍。

    因为g存储在程序上下文中,而程序上下文会随着每一个请求的进入而激活,随着每一个请求的处理完毕而销毁,所以每次请求都会重设这个值。我们通常会使用它结合请求钩子来保存每个请求处理前所需要的全局变量,比如当前登入的用户对象,数据库连接等。在前面的示例中,我们在hello视图中从查询字符串获取name的值,如果每一个视图都需要这个值,那么就要在每个视图重复这行代码。借助g我们可以将这个操作移动到before_request处理函数中执行,然后保存到g的任意属性上:

    * * *

    from flask import g @app.before_request def get_name(): g.name = request.args.get('name')

    * * *

    设置这个函数后,在其他视图中可以直接使用g.name获取对应的值。另外,g也支持使用类似字典的get()、pop()以及setdefault()方法进行操作。

    2.4.2 激活上下文

    阳光柔和,鱼儿在水里欢快地游动,这一切都是上下文存在后的美好景象。如果没有上下文,我们的程序只能直挺挺地躺在鱼缸里。在下面这些情况下,Flask会自动帮我们激活程序上下文:

    ·当我们使用flask run命令启动程序时。

    ·使用旧的app.run()方法启动程序时。

    ·执行使用@app.climand()装饰器注册的flask命令时。

    ·使用flask shell命令启动Python Shell时。

    当请求进入时,Flask会自动激活请求上下文,这时我们可以使用request和session变量。另外,当请求上下文被激活时,程序上下文也被自动激活。当请求处理完毕后,请求上下文和程序上下文也会自动销毁。也就是说,在请求处理时这两者拥有相同的生命周期。

    结合Python的代码执行机制理解,这也就意味着,我们可以在视图函数中或在视图函数内调用的函数/方法中使用所有上下文全局变量。在使用flask shell命令打开的Python Shell中,或是自定义的flask命令函数中,我们可以使用current_app和g变量,也可以手动激活请求上下文来使用request和session。

    如果我们在没有激活相关上下文时使用这些变量,Flask就会抛出RuntimeError异常:“RuntimeError:Working outside of application context.”或是“RuntimeError:Working outside of request context.”。

    提示

    同样依赖于上下文的还有url_for()、jsonify()等函数,所以你也只能在视图函数中使用它们。其中jsonify()函数内部调用中使用了current_app变量,而url_for()则需要依赖请求上下文才可以正常运行。

    如果你需要在没有激活上下文的情况下使用这些变量,可以手动激活上下文。比如,下面是一个普通的Python shell,通过python命令打开。程序上下文对象使用app.app_context()获取,我们可以使用with语句执行上下文操作:

    * * *

    >>> from app import app >>> from flask import current_app >>> with app.app_context(): ... current_app.name 'app'

    * * *

    或是显式地使用push()方法推送(激活)上下文,在执行完相关操作时使用pop()方法销毁上下文:

    * * *

    >>> from app import app >>> from flask import current_app >>> app_ctx = app.app_context() >>> app_ctx.push() >>> current_app.name 'app' >>> app_ctx.pop()

    * * *

    而请求上下文可以通过test_request_context()方法临时创建:

    * * *

    >>> from app import app >>> from flask import request >>> with app.test_request_context('/hello'): ... request.method 'GET'

    * * *

    同样的,这里也可以使用push()和pop()方法显式地推送和销毁请求上下文。

    2.4.3 上下文钩子

    在前面我们学习了请求生命周期中可以使用的几种钩子,Flask也为上下文提供了一个teardown_appcontext钩子,使用它注册的回调函数会在程序上下文被销毁时调用,而且通常也会在请求上下文被销毁时调用。比如,你需要在每个请求处理结束后销毁数据库连接:

    * * *

    @app.teardown_appcontext def teardown_db(exception): ... db.close()

    * * *

    使用app.teardown_appcontext装饰器注册的回调函数需要接收异常对象作为参数,当请求被正常处理时这个参数值将是None,这个函数的返回值将被忽略。

    上下文是Flask的重要话题,在这里我们也只是简单了解一下,在本书的第三部分,我们会详细了解上下文的实现原理。

上一章 目录 下一章