Flask 问题:Blueprint 的 template_folder 设置不起作用

版权声明:所有博客文章除特殊声明外均为原创,允许转载,但要求注明出处。

按照规定,Flask 的模板默认放在 app 所属的 templates 子目录下,但也可以通过构造参数 template_folder (以及类似的 static_folder)来修改。同时,在 Blueprint 的构造方法中也包含同样的 template_folder/static_folder 参数。我从前并未真正修改过这些参数,按照一般的理论推测,既然 Blueprint 提供了这些参数,那么它应该覆盖 app 级别的配置才对。

这几天要写一个 Flask 程序,由于相关模块较多,都堆在一起找起来很麻烦,也容易产生无疑的耦合。因此我考虑采用另外一种代码布局,即把每个 Blueprint 和它所使用的模板一起存放,同时和其他模块分离开来。哪知道,修改以后才发现这样无法工作:不管怎么配置 App/Blueprint 的参数,所有 index.html 都会显示同一个页面。

既然问题已经出现,那就搞清楚它的原因吧。找来找去,在 Flask 的官方 Issue 里发现了线索(看来遇到同样问题的人还真不少):

Method render_template does not use blueprint specified template_folder

从该 Issue 的讨论可见,这个问题是客观存在的,并且有人很不客气地将这称之为一个巨大的问题(huge bug)。但 Pallets 的官方回复则表示,这是一个预期中的行为,App 的查找规则会覆盖 Blueprint 的规则。并且官方也明确表态不会修改此逻辑,因为某些第三方扩展的实现是依赖于该规则的,修改代码会破坏这些扩展的行为(因此不能改)。

从技术上讲,Blueprint 一旦注册完毕,后续路由和模板的查找工作其实是由 app 接管了,blueprint 本身并没有更多的控制权,也无法提供我们想要的那种灵活性了。这一点其实并不出人意外;我以前就发现,一旦 register_blueprint() 方法调用完毕,后面再添加 route 是不起作用的。这也从侧面说明,Blueprint 只是注册时的一种管理手段,并不是在运行时作动态分派,运行时的工作都是由 App 完成的,它无法做到按照不同的 Blueprint 去区分处理(或者说不愿意做到)。

当然,其他类似的框架比如 Django 也存在类似的问题。在 Django 中区分不同模块的机制主要是 App,为了避免定位到错误的模板文件,Django 推荐的做法是在 templates 目录下再添加一个按照 App 命名的子目录,而在视图层面明确指明路径,比如 render('myapp/index.html')。不过 Django 要好过 Flask 的一点是,它的模板路径是在 settings 文件中全局配置的,并不允许每个 App 去单独指定自己的 template_folder,因此也就不会产生歧义。而 Flask 既然提供了这个配置参数但又不起作用,也难怪用户会有不满了。

顺便批评一下,Flask 框架目前的维护者现在是 Pallets(从前是 Pocoo)。对于用户提出的问题,Pallets 方面可能也厌倦了反复解释,直接说了一句 “这是预期的行为,不是 bug” 就关闭了讨论,也没有提供解决办法(倒是其他程序员提出了一些思路,虽然都比较绕)。官方可以认为这不是 bug,但局部配置会被全局配置覆盖,这种设计的确违反直觉,也是不合理的。Pallets 的开发者在这个 Issue 里的态度多少让我觉得有点简单粗暴,令人遗憾。

Flask 的确是一个简单灵活的框架,只要是写 Web 程序,我自己也会优先考虑它。但在使用以后,我也感觉到它的一些缺憾,尤其是设计上比较“僵硬”,过分依赖于明确的初始化顺序,一旦划分模块以后就很容易出现循环依赖或者 ImportError 的问题,这都是设计不良的表现。而其他一些第三方扩展也继承了这个毛病,导入的东西越多,耦合越严重,要实现灵活的模块结构也变得极其困难。

我期待着未来有新的框架能取代 Flask。然而到目前为止,看到的 Web 框架大都无法超越 Django 或 Flask 的设计思路,只能说是某些局部有所改良,因此缺乏去使用的动力。当然这也可能是我的眼界所限,如果你知道有什么优秀的 Python Web 框架的话,也欢迎告诉我。:)