Python 多到数不过来的 Web 框架已经成为了一大风景,而且不同于 PHP Frameworks 集体山寨 Rails 的风格,几乎每个 Python 框架都有自己的特色。我接触过的有 web.py、Django、Bottle、Flask ,其中属 Flask 最为我喜欢。有时候框架会被称为“轮子”,但是可以确定的一点是这四个框架一定不是轮子,我最喜欢的 Flask 有许多非常方便的特性,当然也有我想吐槽的不爽点。于是写一篇博客把吐槽记录下来。
闪亮亮的点
松耦合的组件
这点是 Flask 和 Django 最不同的地方,冲着这点甚至都不应该把 Flask 叫“框架”(Framework),应该叫“库”(Library)。在使用 Flask 的时候,如果控制得合适,可以让 Flask 只和 Web 层面耦合,其他方面不仅可以不受 Flask 影响,还能任意使用 PyPI 上数量庞大的第三方库。
灵活的 Thread Local 模式
很多 Web 框架(比如 web.py、tornado)用类的方式来做 Web 表现层的控制器,请求上下文资源以类成员的形式提供。这样虽然看起来很“面向对象”,却不利于良好地组织代码结构。Web 层除了表现层还会有许多其他自己编写的工具,比如负责用户登录、注销的服务组件,这些组件多多少少都要操纵 Web 上下文。类控制器需要通过传递上下文对象来达到这一目的,这种传递方式会给开发造成不必要的麻烦,到哪里都要给 Context 留个座位。
很庆幸 Flask 没有采取这种方式,而是使用了 ThreadLocal 模式。这有点类似于 PHP 的原理,每个请求线程内部拥有独立的变量空间,用来保存上下文,请求内可以非常安全地操作这些上下文。
反向路由
反向路由又是 Flask 的一亮点功能,很多框架(比如 Bottle)虽然提供了类似功能,却设计的比较鸡肋。有了这个功能,基本可以告别硬编码 URL 了。
蓝图(Blueprint)
Blueprint 类似于 Django 的 App 概念,是 Web 应用中的“子模块”。不同于 Django App 或者 web.py subapplication 的是,Flask 的 Blueprint 同时支持整合静态资源。这一点是通过配合使用 url_for 反向路由做到的。例如应用中有一个 admin Blueprint,其静态资源的 URI 可以通过 url_for("admin.static", "/scripts/admin.js") 获取,这样就不用分离了模块代码却将静态资源放在一起了。
每一个 Blueprint 对象,对于模块内来说,就像是一个独立的 Flask Application,拆卸、复用都非常方便。
想吐槽的不爽点
模板引擎
前文说了 Flask 有着松耦合的组件,但有一个例外,就是模板引擎。我实在理解不了为何 Flask 要把模板绑死在 Jinja 2 上。Flask 文档中的说法是因为使用了 Jinja 2 独有的高级特性,例如过滤器,可问题是这些高级特性确确实实不是 Jinja 2 独有的,Mako 一样有。做一个适配器应该不是难事,可是 Flask 却选择只绑定自家的 Jinja 2。
Jinja 2 的确是很优秀的模板引擎,可是强制使用 Jinja 2 就让开发者做选择的权利被剥夺了。Mako 等模板是另一种风格,支持直接在模板中写 Python 代码,在不少地方更加灵活,完全有理由给开发者选择的机会。支持 Mako 的第三方 Flask 插件也是存在的,可是因为不是直接嵌入 Flask Application,和其他 Flask 插件的兼容很是问题。
Cookie
另一个搞不懂的是,为何 Flask 对于 Cookie 的支持是原始接口。在 Flask 中使用 Session,只需要像操作普通 dict 一样操作 ThreadLocal 的 session 对象,Flask 最终会将其写入响应头;可是写入一个 Cookie 居然需要自己构造 Response 对象,然后调用 set_cookie 方法。我觉得这是一个非常奇怪的设计,为 Session 提供高级接口却不为 Cookie 提供。要知道,不是什么时候都需要用 Session 这样服务器端会话存储的,有时候为了保存一些用户选项(例如区域、语言),操作 Cookie 却如此麻烦。