概述
在分析 Flask 的模板源码之前,我觉得同学们应该对 Jinja2 这个模板语言已经有了一些了解,至于了解到什么程度,其实,也不用深,只要会用就可以了。例如理清了 Environment
和 Loader
这两个概念就够了吧。如果没有的话,那么可以尝试阅读一下我的另一篇文章《Jinja 环境介绍》,通篇下来,应该可以无压力得理解本文内容。
源码解读
Flask 的模板入口我们就从用法入手,也就是从 render_template
开始,这个函数的定义代码在:flask/templating.py
中:
图 1:模板的渲染入口 |
前面两行先不看,先看一下 _render
做了什么微薄的贡献,_render
就在 render_template
这个函数的上面:
图 2:模板渲染实现 |
这里有两句可以先忽略,就是 115 的 before_render_template
和 117 的 template_rendered
,因为这是两个信号,不影响我们对模板渲染的分析,所以可以先看中间一行,116 这句是直接渲染模板,template
和 context
一样都是传参进来的,ok,那么我们就看一下这个 template
和 context
是从哪里来的,回到调用处: line 133,真相就在于:
template = ctx.app.jinja_env.get_or_select_template(template_name_or_list)
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:增加模板变量 |
添加之后我们就可以在模板中使用了:
{{ 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 都替换掉,那么整个模板不都替换掉了?不过这种方法比较麻烦,需要做的工作比较多,但毫无疑问,效率肯定比软替换高。
相关配置
- TEMPLATES_AUTO_RELOAD:是否自动检查模板的改动,如果设置了,Flask 会自动检查模板的改动并加载新模板。
- EXPLAIN_TEMPLATE_LOADING: 如果设置了这个变量,那么在每次加载一个模板的时候都会答应一条关于模板位置的 info 信息,这个在你调试模板错误或者模板找不到的时候非常有用。