概述

在分析 Flask 的模板源码之前,我觉得同学们应该对 Jinja2 这个模板语言已经有了一些了解,至于了解到什么程度,其实,也不用深,只要会用就可以了。例如理清了 EnvironmentLoader 这两个概念就够了吧。如果没有的话,那么可以尝试阅读一下我的另一篇文章《Jinja 环境介绍》,通篇下来,应该可以无压力得理解本文内容。

源码解读

Flask 的模板入口我们就从用法入手,也就是从 render_template 开始,这个函数的定义代码在:flask/templating.py 中:

图 1:模板的渲染入口

前面两行先不看,先看一下 _render 做了什么微薄的贡献,_render 就在 render_template 这个函数的上面:

图 2:模板渲染实现

这里有两句可以先忽略,就是 115 的 before_render_template 和 117 的 template_rendered,因为这是两个信号,不影响我们对模板渲染的分析,所以可以先看中间一行,116 这句是直接渲染模板,templatecontext 一样都是传参进来的,ok,那么我们就看一下这个 templatecontext 是从哪里来的,回到调用处: line 133,真相就在于:

  1. template = ctx.app.jinja_env.get_or_select_template(template_name_or_list)
  2. context = context

这个 context 倒是明显,就是 context,但是,往前回溯一行,会发现在 line 132 context 被 update 过,这又变得有点复杂了,但是,即使复杂还是要跟下去。

先看 ctx 是什么,在 line 131 可以看到这是应用上下文,然后,再跟一下它是如何更新context 的,我们跟进更新代码,在 flask/app.py line 745 处:

图 3:模板渲染上下文

这段代码看上去很复杂,其实功能很简单,就是往 context 中加入了 g/request/session,注意,我们能够在模板上使用这些变量是因为在这里就加入到 context 中了。

然后继续看下去,回到 flask/templating.py line 133,我们看看另外一个变量 template 又是怎么一回事,ctx.app 我们知道是 Flask 对象,那么 ctx.app.jinja_env 是什么对象呢,跟一下源码,flask/app.py line 626:

图 4:jinja 的渲染环境
图 5:jinja 渲染环境实现

这里的代码事实上创建了一个 Environment 对象,而这个 Environment 不是 Jinja 默认的,而是 Flask 自己定义的,定义的代码在 flask/templating.py line 33。同时需要注意的是这里也添加了很多模板中使用的变量,我们可以看到在模板中使用的函数/变量都在这里出现了:url_for、get_flashed_messages、config、g 等,奇怪的是,这里只加了一个 tojson 过滤器,是因为这个过滤器特别有用吗?

还有一个非常重要的点就是,Loader 是谁,在哪里设置的,我们回到刚才创建 Environment 代码的位置,因为之前说了这个 Environment 是自定义的,所以看回 Environment 的代码,可以发现在 flask/templating.py line 40 这里用的 loader 也是自定义的,就是 line 46 中的 DispatchingJinjaLoader。这里有点意思的地方是 Flask 提供了两种加载模板的方式,一种是“快速” 的,也就是 line 79 中的 _get_source_fast 以及“解释型”的,代码是 line 59 的 _get_source_explained,“解释型” 的会在每次调用 render_template 的时候打印信息。

就这样,整个渲染过程就通了,关键还是两个:Environment 和 Loader,这两个概念都是 Flask 自定义过的,然后还需要关注模板中使用的变量是在哪里放进去的。但是,我们走的这条主线还有一些旁带问题有疑问,如果只想知道模板渲染过程的同学看到这已经够了,下面的内容将是给好奇心比较重的同学扩展用。

一些疑问

1. 如何增加模板渲染的变量

这个问题对于使用过 Flask 的同学来说应该都不大,我们看看 render_template 的定义就知道了,最后一个参数是一个 **context,所以我们可以直接在 render_template 的时候增加变量就可以了。

那么有没有方法可以不用每次都在 render_template 的时候设置,而是默认得自动给我加上,在看过代码之后,我们知道应该是可以的,我们可以在创建 Environment 中的过程中某个变量中插入自己的变量就行啦,这样以后每次就可以自动加载了,那么这个变量是谁合适,看一下 Flask 的文档,发现 Flask 已经给我们设计好了,就是:template_context_processors。但是,我们并不能直接修改他的值,而是通过装饰器的方式添加,下面就分别举一个添加 变量 和 方法的例子:

图 6:增加模板变量

图 7:增加模板变量

添加之后我们就可以在模板中使用了:

  1. {{ format_price(0.33) }}

当然,同样的功能也可以通过过滤器实现。

2. 一开始中的 before_render_template 和 template_rendered,有什么用?有谁在用?

这两个信号目前在 Flask 内部是没有使用的,至于用处的话,在上面提到过一个“解释型”加载模板的方法,那么,我们也可以使用“快速”加载,然后连接这两个信息打印模板信息也能实现相同的效果。

3. 如何添加自定义过滤器

参考文档 Templates 我们可以看到自定义过滤器还是很简单的,文档中提供了两种方式,分别是:

图 8:装饰器过滤器

和字典添加式:

图 9:字典过滤器

第二种我们前面看过源码,一看就知道怎么起作用的了,对于第一种,我们看一下是如何起到作用的,在 flask/app.py line 1207:

图 10:装饰器应用

其实说到底也是和第二种一样,只不过以一种更方便的方式服务。

4. 可不可以替换掉 Jinja 模板而使用其他模板

虽然说 Flask 是和 Jinja2 绑定了,但是,毫无疑问我们是可以替换掉 Jinja 模板的,例如我的博客就是使用的 Jade 模板。

替换的方法我知道的有两种,一种是软替换,也就是将你想要的模板转换成 jinja 模板,然后再被 Flask 渲染,我博客使用的就是这种方式;第二种就是硬替换,我们一开始就说从 render_template 开始,要是我们将 render_template 都替换掉,那么整个模板不都替换掉了?不过这种方法比较麻烦,需要做的工作比较多,但毫无疑问,效率肯定比软替换高。

相关配置