Flask设计思路

为什么 Flask 要这样做,而不是那样做?如果你对这点好奇,那么本节可以满足你 的好奇心。当与其他框架直接进行比较时, Flask 的设计思路乍看可能显得武断并 且令人吃惊,下面我们就来看看为什么在设计的时候进行这样决策。

显式应用程序对象

一个基于 WSGI 的 Python web 应用必须有一个实现实际的应用的中心调用对象。在 Flask 中,中心调用对象是一个 Flask 类的实例。每个 Flask 应 用必须创建一个该类的实例,并且把模块的名称传递给该实例。但是为什么 Flask 不自动把这些事都做好呢?

如果没有一个显式的应用对象,那么会是这样的:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'

请改为:

from hypothetical_flask import route

@route('/')
def index():
    return 'Hello World!'

使用对象的主要有三个原因。最重要的一个原因是显式对象可以保证实例的唯一性。 有很多方法可以用单个应用对象来冒充多应用,比如维护一个应用堆栈,但是这样将 会导致一些问题,这里我就不展开了。现在的问题是:一个微框架何时会需要多应用? 最好的回答是当进行单元测试的时候。在进行测试时,创建一个最小应用用于测试特 定的功能,是非常有用的。当这个最小应用的应用对象被删除时,将会释放其占用的 所有资源。

另外当使用显式对象时,你可以继承基类( Flask ), 以便于修改特定的功能。如果不使用显式对象,那么就无从下手了。

第二个原因也很重要,那就是 Flask 需要包的名称。当你创建一个 Flask 实例时, 通常会传递 __name__ 作为包的名称。 Flask 根据包的名称来载入也模块相关的 正确资源。通过 Python 杰出的反射功能,就可以找到模板和静态文件(参见 open_resource() )。很显然,有其他的框架不需要任何配置 就可以载入与模块相关的模板。但其前提是必须使用当前工作目录,这是一个不可靠 的实现方式。当前工作目录是进程级的,如果有多个应用使用同一个进程( web 服 务器可能在你不知情的情况下这样做),那么当前工作目录就不可用了。还有更糟糕 的情况:许多 web 服务器把文档根目录作为当前工作目录,如果你的应用所在的目 录不是文档根目录,那么就会出错。

第三个原因是“显式比隐式更好”。这个对象就是你的 WSGI 应用,你不必再记住其 他东西。如果你要实现一个 WSGI 中间件,那么只要封装它就可以了(还有更好的方 式,可以不丢失应用对象的引用,参见: wsgi_app() )。

此外,这种设计使得使用工厂函数来创建应用程序成为可能,这对于单元测试和类似的事情非常有帮助。( 应用工厂

路由系统

Flask 使用 Werkzeug 路由系统,该系统是自动根据复杂度来为路由排序的。也就是 说你可以以任意顺序来声明路由,路由系统仍然能够正常工作。为什么要实现这个功 能?因为当应用被切分成多个模块时,基于路由的装饰器会以乱序触发,所以这个功 能是必须的。

另一点是 Werkzeug 路由系统必须确保 URL 是唯一的,并且会把模糊路由重定向到 标准的 URL 。

一个模板引擎

Flask 原生只使用 Jinja2 模板引擎。为什么不设计一个可插拔的模板引擎接口?当 然,你可以在 Flask 中使用其他模板引擎,但是当前 Flask 原生只会支持 Jinja2 。 将来也许 Flask 会使用其他引擎,但是永远只会绑定一个模板引擎。

模板引擎类似于编程语言,这些引擎中的每一个都对事物的工作方式有一定的了解。从表面上看,它们的工作原理都是一样的:您告诉引擎使用一组变量评估一个模板,并将返回值作为字符串。

但是相似之处也仅限于此。例如 Jinja2 有丰富的过滤系统、有一定的模板继承能力、 支持从模板内或者 Python 代码内复用块(宏)、所有操作都使用 Unicode 、支持 迭代模板渲染以及可配置语法等等。而比如 Genshi 基于 XML 流赋值,其模板继承 基于 XPath 的能力。再如 Mako 使用类似 Python 模块的方式来处理模板。

当涉及到将模板引擎与应用程序或框架连接时,不仅仅是呈现模板。例如,flask使用了jinja2的广泛自动转义支持。它还提供了从jinja2模板访问宏的方法。

一个不失模板引擎独特性的模板抽象层本身就是一门学问,因此这不是一个 Flask 之类的微框架应该考虑的事情。

此外,扩展可以很容易地依赖于存在的一种模板语言。您可以轻松地使用自己的模板语言,但是扩展仍然依赖于Jinja本身。

具有依赖关系的Micro

为什么 Flask 依赖两个库( Werkzeug 和 Jinja2 ),但还是自称是微框架?为什 么不可以呢?如果我们看一看 Web 开发的另一大阵营 Ruby ,那么可以发现一个与 WSGI 十分相似的协议。这个协议被称为 Rack ,除了名称不同外,基本可以视作 Ruby 版的 WSGI 。但是几乎所有 Ruby 应用都不直接使用 Rack 协议,而是使用一 个相同名字的库。在 Python 中,与 Rack 库等价的有 WebOb (前身是 Paste )和 Werkzeug 两个库。 Paste 任然可用,但是个人认为正逐步被 WebOb 取代。WebOb 和 Werkzeug 的开发初衷都是:做一个 WSGI 协议的出色实现,让其他应用受益。

正因为 Werkzeug 出色地实现了 WSGI 协议(有时候这是一个复杂的任务),使得依 赖于 Werkzeug 的 Flask 受益良多。同时要感谢 Python 包管理的近期开发,包依 赖问题已经解决,几乎没有理由不使用包依赖的方式。

线程局部

Flask 使用线程本地对象(实际是上下文本地对象,它们也支持 greenlet 上下文) 来支持请求、会话和一个可以放置你自己的东西的额外对象( g )。 为什么要这样做?这不是一个坏主意吗?

是的,使用线程局部变量通常不是一个好主意。它们会给不基于线程概念的服务器带来麻烦,使大型应用程序更难维护。然而,flask并不适用于大型应用程序或异步服务器。Flask希望能够快速方便地编写传统的Web应用程序。

一些关于基于 Flask 大型应用的灵感,见文档的 大型应用 一节。

Flask 是什么,不是什么

Flask永远不会有数据库层。它将没有表单库或该方向的任何其他内容。flask本身只是连接到werkzeug以实现适当的wsgi应用程序,并连接到jinja2以处理模板。它还绑定到一些常见的标准库包,如日志记录。其他一切都可以延期。

为什么会这样?因为人们有不同的偏好和要求, Flask 不可能把所有的需求都囊括在核 心里。大多数 web 应用会需要一个模板引擎。然而不是每个应用都需要一个 SQL 数 据库的。

Flask的思想是为所有的应用奠定良好的基础。其他一切都取决于您或扩展。