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代码中使用,支持迭代模板渲染、可配置语法等。另一方面,像Genshi这样的引擎是基于XML流计算、考虑XPath可用性的模板继承等。另一方面,Mako处理模板类似于Python模块。

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

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

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

“微”是什么意思?

“Micro”并不意味着您的整个Web应用程序必须放在一个单独的Python文件中(尽管它肯定可以),也不意味着FlaskTM缺乏功能。微框架中的“微”意味着FlASK旨在保持核心的简单但可扩展。FASK不会为您做出很多决定,比如使用哪个数据库。它所做的决定,例如使用什么模板引擎,都很容易更改。其他一切都由你来决定,这样你就可以拥有你需要的一切,而不是你不需要的东西。

默认情况下,Flask不包括数据库抽象层、表单验证或任何其他已经存在的可以处理该问题的库。相反,FlASK支持扩展以将此类功能添加到您的应用程序中,就像它是在Flask自身中实现的一样。大量扩展提供数据库集成、表单验证、上传处理、各种开放身份验证技术等。烧瓶可能是“微型”的,但它已经准备好用于各种需求的生产。

为什么 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应用程序。

异步/等待和ASGI支持

烧瓶支架 async 通过在单独的线程上执行协程来实现视图函数的协程,而不是像异步优先(ASGI)框架那样在主线程上使用事件循环。为了保持与之前构建的扩展和代码的向后兼容性,这是必要的 async 被引入到了Python中。由于线程的开销,这种折衷引入了与ASGI框架相比的性能成本。

由于与WSGI Flask代码的绑定程度很高,目前还不清楚是否可能使 Flask 类同时支持ASGI和WSGI。Werkzeug目前正在与ASGI合作,这可能最终也会在Flask中实现支持。

看见 vbl.使用 async 和 await 以供更多讨论。

Flask 是什么,不是什么

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

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

随着代码库的增长,您可以自由地做出适合您的项目的设计决策。FlASK将继续提供一个非常简单的粘合层,以达到Python所能提供的最好效果。您可以在SQLAlChemy或其他数据库工具中实现高级模式,适当地引入非关系数据持久性,并利用为WSGI构建的与框架无关的工具。

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