Flask 中的设计决策 — Flask 文档
Flask 中的设计决策
如果您想知道为什么 Flask 以它所做的方式而不是不同的方式做某些事情,那么本节适合您。 这应该让您了解一些最初可能显得武断和令人惊讶的设计决策,尤其是在与其他框架的直接比较时。
显式应用程序对象
基于 WSGI 的 Python Web 应用程序必须有一个中央可调用对象来实现实际应用程序。 在 Flask 中,这是 Flask
类的一个实例。 每个 Flask 应用程序都必须自己创建这个类的一个实例并将模块的名称传递给它,但为什么 Flask 不能自己做呢?
如果没有这样一个明确的应用程序对象,下面的代码:
看起来像这样:
这有三个主要原因。 最重要的一点是隐式应用程序对象要求一次只能有一个实例。 有很多方法可以使用单个应用程序对象来伪造多个应用程序,例如维护应用程序堆栈,但这会导致一些问题,我不会在这里详细概述。 现在的问题是:一个微框架何时需要同时使用多个应用程序? 一个很好的例子是单元测试。 当您想要测试某些东西时,创建一个最小的应用程序来测试特定的行为会非常有帮助。 当应用程序对象被删除时,它分配的所有内容都将被再次释放。
当您的代码中存在显式对象时,另一件可能的事情是您可以对基类 (Flask
) 进行子类化以改变特定行为。 如果对象是基于未向您公开的类提前为您创建的,那么如果没有 hack,这将是不可能的。
但是 Flask 依赖于该类的显式实例化还有另一个非常重要的原因:包名。 每当您创建 Flask 实例时,您通常会将其 __name__ 作为包名传递。 Flask 依赖于该信息来正确加载与您的模块相关的资源。 凭借 Python 对反射的出色支持,它可以访问包以找出模板和静态文件的存储位置(参见 open_resource()
)。 现在显然有一些框架不需要任何配置,并且仍然能够加载与您的应用程序模块相关的模板。 但是他们必须为此使用当前工作目录,这是确定应用程序位置的一种非常不可靠的方法。 当前工作目录是进程范围的,如果您在一个进程中运行多个应用程序(这可能发生在您不知道的网络服务器中),路径将关闭。 更糟糕的是:许多网络服务器不会将工作目录设置为您的应用程序目录,而是设置为不必是同一文件夹的文档根目录。
第三个原因是“显式优于隐式”。 该对象是您的 WSGI 应用程序,您不必记住任何其他内容。 如果你想应用一个 WSGI 中间件,只需将它包装起来就完成了(尽管有更好的方法来做到这一点,这样你就不会丢失对应用程序对象 wsgi_app()
的引用)。
此外,这种设计使得使用工厂函数来创建应用程序成为可能,这对单元测试和类似的事情非常有帮助(应用程序工厂)。
路由系统
Flask 使用 Werkzeug 路由系统,该系统旨在按复杂性自动排序路由。 这意味着您可以按任意顺序声明路由,并且它们仍会按预期工作。 如果您想正确实现基于装饰器的路由,这是一项要求,因为当应用程序拆分为多个模块时,装饰器可能会以未定义的顺序触发。
Werkzeug 路由系统的另一个设计决策是 Werkzeug 中的路由尝试确保 URL 是唯一的。 Werkzeug 将在这方面走得更远,因为如果路由不明确,它将自动重定向到规范 URL。
一个模板引擎
Flask 决定使用一种模板引擎:Jinja2。 为什么 Flask 没有可插拔的模板引擎接口? 您显然可以使用不同的模板引擎,但 Flask 仍会为您配置 Jinja2。 虽然 Jinja2 总是 配置的限制可能会消失,但捆绑一个模板引擎并使用它的决定不会。
模板引擎就像编程语言,每个引擎都对事物的工作方式有一定的了解。 从表面上看,它们的工作方式都是一样的:你告诉引擎用一组变量评估一个模板,并将返回值作为字符串。
但这就是相似之处的结束。 例如,Jinja2 有一个广泛的过滤系统,一定的模板继承方式,支持可重用块(宏),可以从模板内部使用,也可以从 Python 代码使用,使用 Unicode 进行所有操作,支持迭代模板渲染,可配置语法和更多。 另一方面,像 Genshi 这样的引擎基于 XML 流评估、考虑 XPath 可用性的模板继承等等。 另一方面,Mako 对待模板类似于 Python 模块。
在将模板引擎与应用程序或框架连接时,不仅仅是渲染模板。 例如,Flask 使用 Jinja2 广泛的自动转义支持。 它还提供了从 Jinja2 模板访问宏的方法。
一个模板抽象层不会带走模板引擎的独特功能,它本身就是一门科学,对于像 Flask 这样的微框架来说是一项太大的任务。
此外,扩展可以很容易地依赖于存在的一种模板语言。 您可以轻松使用自己的模板语言,但扩展仍可能依赖于 Jinja 本身。
具有依赖关系的微型
为什么 Flask 称自己为微框架,但它依赖于两个库(即 Werkzeug 和 Jinja2)。 为什么不应该呢? 如果我们看一下 Web 开发的 Ruby 方面,我们有一个与 WSGI 非常相似的协议。 只是它在那里被称为 Rack,但除此之外,它看起来非常像 Ruby 的 WSGI 版本。 但是几乎所有 Ruby 领域的应用程序都不直接与 Rack 一起工作,而是在同名的库之上。 这个 Rack 库在 Python 中有两个等价物:WebOb(以前称为 Paste)和 Werkzeug。 Paste 仍然存在,但根据我的理解,它已被弃用,而支持 WebOb。 WebOb 和 Werkzeug 的开发并肩开始时怀有类似的想法:成为 WSGI 的良好实现,以便其他应用程序利用。
Flask 是一个框架,它利用 Werkzeug 已经完成的工作来正确连接 WSGI(有时这可能是一项复杂的任务)。 由于 Python 包基础结构的最新发展,具有依赖项的包不再是一个问题,并且几乎没有理由反对拥有依赖于其他库的库。
线程局部变量
Flask 使用线程本地对象(实际上,上下文本地对象,它们也支持 greenlet 上下文)用于请求、会话和一个额外的对象,您可以将自己的东西放在(g)上。 为什么会这样,这不是一个坏主意吗?
是的,使用线程局部变量通常不是一个好主意。 它们会给不基于线程概念的服务器带来麻烦,并使大型应用程序更难维护。 然而 Flask 并不是为大型应用程序或异步服务器设计的。 Flask 希望能够快速轻松地编写传统的 Web 应用程序。
另请参阅文档的 Becoming Big 部分,以获得基于 Flask 的大型应用程序的一些灵感。
Flask 是什么,Flask 不是什么
Flask 永远不会有数据库层。 它不会有表单库或该方向的任何其他内容。 Flask 本身只是连接到 Werkzeug 以实现适当的 WSGI 应用程序,并连接到 Jinja2 来处理模板。 它还绑定到一些常见的标准库包,例如日志记录。 其他一切都是为了扩展。
为什么会这样? 因为人们有不同的偏好和要求,如果 Flask 将其中任何一个强加到核心中,则 Flask 无法满足这些要求。 大多数 Web 应用程序都需要某种形式的模板引擎。 然而,并非每个应用程序都需要 SQL 数据库。
Flask 的想法是为所有应用程序建立一个良好的基础。 其他一切都取决于您或扩展程序。