python模板引擎的基本原理

这两天简单研究了一下web.py框架,发现比较有意思的就是template模板引擎的实现了,所以简单研究了一下基本原理。

例子

我们先看一个web.py的模板例子。

我有这样一个模板文件:

这是一个HTML文件,凡是动态内容,均需要以$开头标识出来,以便模板引擎解析替换。

  • $def with (words):这是定义了python要传进来的动态参数,在<h1>$words</h1>中渲染。
  • $:render.header():这是调用了render对象的header()方法,作用是渲染另外一个模板文件。
  • $for i in range(0,10):通过循环制作一个列表。

按照简单理解,python只需要把模板文件读进内存,然后用正则去替换一下$words这样的东西就好了。但是复杂就是,模板引擎还允许执行python代码,比如调用header()函数,for循环,这种用正则怎么可能处理的了呢?所以,模板引擎的实现思路就特别重要了。

模板 -> 代码

如何让模板引擎可以执行Python代码呢?我们不如先看看web.py是如何渲染的。

web.py对上述模板文件经过一番处理,得到的实际上就是一段python代码:

观察可以发现,这个函数就是HTML模板文件的python实现:

  • 模板的入参变成了__template__函数的入参
  • 文本的HTML代码变成了python不断extend_追加字符串的过程
  • 嵌套渲染其他模板变成了header()函数调用,结果又extend_到最终结果中去
  • 循环也变成了真的python代码循环

实际上,模板引擎要做的就是对模板文件进行语法解析,把普通文本转换为extend_(xxx),把python表达式原样的挪过来,最后拼成一个可执行的python函数。

当然,这个解析过程涉及到语法解析的知识,我个人不擅长,所以不展开研究了。

编译代码

当前我们通过语法解析,生成了上述的一段python代码(是一段文本而已),这段代码做的唯一的事情就是定义了一个__template__函数。

现在我们要用这个函数来渲染模板得到最终的HTML,所以需要执行这段代码,得到真正的python可执行函数。

我把这个事情简化一下,现在我们要做的事情是先编译这段代码:

code中的文本代码,被编译成了AST抽象语法表达树,但是当前还没有被执行。

执行代码

如果code中的代码放在一个.py文件中,我们知道直接调用f()就可以了,但是现在code只是一段字符串,这时候如何执行代码呢?

这时候我们需要用到exec方法,它可以动态执行一段代码,同时允许单独指定代码执行的上下文环境:

exec第一个参数是代码,第二个参数是所处的全局环境,第三个代码是所处的局部环境。

我们知道,当在全局作用域定义函数的时候,函数会被保存到globals()中;在局部作用域(比如某个函数内)定义函数的时候,函数会被保存到locals()中。

所以我们定义一个空字典global_env当做代码执行的全局环境,那么f函数就会保存到global_env中。

调用函数

现在,我们可以从global_env中取出f函数,直接调用它即可:

这就是模板引擎的基本工作原理了,所以模板引擎一般都会把编译好的__template__函数cache起来复用,因为如果每次都去解析模板文件再生成可执行函数的消耗有点大。

发表评论

电子邮件地址不会被公开。