应用上下文

0.9 新版功能.

Flask背后的设计理念之一是,代码执行有两种不同的“状态”。应用建立状态,它是应用在模块级别的隐式状态。它起始于Flask对象实例化的时候,隐式地结束于第一个请求进来。当应用处于这个状态的时候,有下面一些假设为真:

  • 程序员可以安全地修改应用对象
  • 目前还没有处理任何请求
  • 不会有某个神奇的代理变量指向你刚创建的或者正在修改的应用对象的

相反,在请求处理时,存在一些其它的规则:

  • 当一个请求激活时,上下文的本地对象( flask.request 和其它对象等) 指向当前的请求
  • 你可以在任何时间里使用任何代码与这些对象通信

这里有一个第三种情况,有一点点差异。 有时,你正在用类似请求处理时方式来 与应用交互,即使并没有活动的请求。 想象一下你用交互式 Python shell 与应用 交互的情况,或是一个命令行应用的情况。

current_app 上下文本地变量就是应用上下文驱动的。

应用上下文的目的

应用上下文存在的主要原因是,在过去,请求上下文被附加了一堆函数,但是又没有什么好的解决方案。 因为 Flask 设计的支柱之一是你可以在一个 Python 进程中拥有多个应用。

那么代码如何找到“正确的”应用? 在过去,我们推荐显式地到处传递应用,但是这会造成库并不是按照我们想法设计的问题。

解决上述问题的常用方法是使用后面将会提到的 current_app 代 理对象,它被绑定到当前请求的应用的引用。 既然无论如何在没有请求时创建一个 这样的请求上下文是一个没有必要的昂贵操作,应用上下文就被引入了。

创建应用上下文

有两种方式来创建应用上下文。 第一种是隐式的:无论何时当一个请求上下文被压栈时, 如果有必要的话一个应用上下文会被一起创建。 由于这个原因,你可以忽略应用 上下文的存在,除非你需要它。

第二种是显式地调用 app_context() 方法:

from flask import Flask, current_app

app = Flask(__name__)
with app.app_context():
    # within this block, current_app points to app.
    print current_app.name

在配置了 SERVER_NAME 时,应用上下文也被用于 url_for() 函 数。 这允许你在没有请求时生成 URL 。

如果没有推送请求上下文,并且没有明确设置应用程序上下文,则会引发RuntimeError

RuntimeError: Working outside of application context.

上下文的局部性

应用上下文会在必要时被创建和销毁。 它不会在线程间移动,并且也不会在不同的请求 之间共享。 正因为如此,它是一个存储数据库连接信息或是别的东西的最佳位置。 内部 的栈对象叫做 flask._app_ctx_stack扩展可以在最顶层自由地存储额外信息,想象一下它们用一个充分独特的名字在那里存储信息,而不是在flask.g对象里,它是留给用户的代码用的。

更多详情见Flask扩展开发

上下文的用处

上下文的一个典型应用场景就是用来缓存一些我们需要在发生请求之前或者要使用的资源。 举个例子,比如数据库连接。 当我们在应用上下文中来存储东西的时候你 得选择一个唯一的名字,这是因为应用上下文为 Flask 应用和扩展所共享。

最常见的应用就是把资源的管理分成如下两个部分:

  1. 一个缓存在上下文中的隐式资源
  2. 当上下文被销毁时重新分配基础资源

通常来讲,这将会有一个get_X()函数来创建资源X ,如果它还不存在的话,存在的话就直接返回它,另外还会有一个teardown_X()的回调函数用于销毁。

如下是我们刚刚提到的连接数据库的例子:

import sqlite3
from flask import g

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = connect_to_database()
    return db

@app.teardown_appcontext
def teardown_db(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

get_db() 这个函数第一次被调用的时候数据库连接已经被建立了。 可以使用LocalProxy隐式地做到这点:

from werkzeug.local import LocalProxy
db = LocalProxy(get_db)

这样的话,用户可以直接访问db,而它在内部调用get_db()