β

python模板引擎的基本原理

鱼儿的博客 8 阅读

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

例子

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

我有这样一个模板文件:

$def with (words)
<!DOCTYPE html>
<html lang="en">
<head>
    <title>web-py</title>
    $:render.header()
</head>
<body>
    <h1>$words</h1>
    <ul>
        $for i in range(0, 10):
            <li>$i</li>
    </ul>
</body>
</html>

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

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

模板 -> 代码

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

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

# coding: utf-8
def __template__ (words):
    __lineoffset__ = -4
    loop = ForLoop()
    self = TemplateResult(); extend_ = self.extend
    extend_(['\n'])
    extend_(['<!DOCTYPE html>\n'])
    extend_(['<html lang="en">\n'])
    extend_(['<head>\n'])
    extend_(['    <title>web-py</title>\n'])
    extend_(['    ', escape_(render.header(), False), '\n'])
    extend_(['</head>\n'])
    extend_(['<body>\n'])
    extend_(['    <h1>', escape_(words, True), '</h1>\n'])
    extend_(['    <ul>\n'])
    for i in loop.setup(range(0, 10)):
        extend_(['        ', '<li>', escape_(i, True), '</li>\n'])
    extend_(['    </ul>\n'])
    extend_(['</body>\n'])
    extend_(['</html>\n'])
    return self

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

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

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

编译代码

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

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

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

# -*- coding: utf-8 -*-
# 纯文本代码
code = """
def f(x):
    return x * x
"""
# 编译文本代码, 生成AST抽象语法树
ast = compile(code, '', 'exec')

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

执行代码

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

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

# 执行代码
global_env = {}
exec(ast, global_env)

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

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

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

调用函数

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

# 函数f就被定义到global_env中了, 我们可以取出来可执行函数
f = global_env['f']
# 调用它
result = f(5)
print(result)

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

作者:鱼儿的博客
斯是陋室,惟吾德馨
原文地址:python模板引擎的基本原理, 感谢原作者分享。

发表评论