python模板引擎的基本原理

栏目: Python · 发布时间: 6年前

内容简介:这两天简单研究了一下web.py框架,发现比较有意思的就是template模板引擎的实现了,所以简单研究了一下基本原理。我们先看一个web.py的模板例子。我有这样一个模板文件:

这两天简单研究了一下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文件,凡是动态内容,均需要以$开头标识出来,以便模板引擎解析替换。

  • $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代码:

# 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实现:

  • 模板的入参变成了__template__函数的入参
  • 文本的HTML代码变成了python不断extend_追加字符串的过程
  • 嵌套渲染其他模板变成了header()函数调用,结果又extend_到最终结果中去
  • 循环也变成了真的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起来复用,因为如果每次都去解析模板文件再生成可执行函数的消耗有点大。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

从规范出发的程序设计

从规范出发的程序设计

[美] Carroll Morgan / 裘宗燕 / 机械工业出版社 / 2002-8 / 45.00元

本书详细论述了有关规范程序设计的内容,包括:程序和精化、谓词演算、选择、迭代、构造类型、模块和封装等,最后几章还包含了大量的实例研究和一些更高级的程序设计技术。本书提倡一种严格的程序开发方法,分析问题要用严格方式写出程序的规范,而后通过一系列具有严格理论基础的推导,最终得到可以运行的程序。 本书是被世界上许多重要大学采用的教材,适于计算机及相关专业的本科生和研究生使用。一起来看看 《从规范出发的程序设计》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具