正文
return
''
.
join
(
result
)
每一个模板都会被转换成一个
render_function
函数,函数接收一个名称为
context
的字典。函数内部首先对字典进行解包,然后将
context
转化为本地局部变量,因为转换之后可以更快的进行多次调用。所有本地局部变量冠以
c_
前缀。
函数的运行结果是一个字符串。最快速的通过字符串片段生成字符串的方式就是先建立一个字符串列表,然后通过 join 函数生成结果字符串。
result
就是这个字符串列表,因为我们还需要对
result
列表进行
expand
和
extend
操作,于是我们把这两个方法也本地化成
append_result
和
extend_result
,这样可以得到更高效率的重复调用。我们最后还将
str
函数本地化了,因为这个函数也需要多次调用。
虽然这样的本地化的方式在 Python 中并不常用,但是因为节省了函数查找的时间,可以得到更好的执行效率。
# The way we're used to seeing it:
result.append("hello")
# But this works the same:
append_result = result.append
append_result("hello")
这是一个比较简单的优化技巧:通过非常规编程得到更好的运行效率,这些优化虽然不宜读,可能还有些复杂,但是却对程序运行的效率有明显改善。但即使如此,有些技巧也勿滥用,毕竟它影响了程序的可读性。
一旦我们定义好这些局部函数,我们就可以对他们进行调用,比如通过
append_result
或
extend_result
对 result 列表进行操作。
同时使用
expand
和
extend
可能有点混乱,但是我们的目标是尽可能高的执行效率,通过 extend 可以生成一个性的列表,这个列表可以再次被传递到 extend 以便执行下次循环。
{{
...
}}
中的语句会被执行,转换成字符串,添加到结果,而点号会被
do_dots
函数单独处理,因为这里面点号可能有多种含义,可能是字典元素,或是对象属性,也可能是可执行方法。
逻辑代码块
{%
if
...
%}
和
{%
for
...
%}
被转换成 Python 的条件和循环语句。其中的表达式会变成
if
和
for
语句中的表达式,而中间直到
{%
end
...
%}
将会转换为
if
或
for
的代码块.
引擎的编写
前面我们已经了解了模板引擎的实现方法,现在我们开始着手实现这个引擎。
Templite 类
模板引擎的核心就是这个 Templite 类(Template Lite)
Templite 有一个小的接口。一旦你构造了这样一个类,后面就可以通过调用
render
方法实现对特定
context
(内容字典) 的渲染:
# Make a Templite object.
templite = Templite('''
Hello {{name|upper}}!
{% for topic in topics %}
You are interested in {{topic}}.
{% endfor %}
''',
{'upper': str.upper},
)
# Later, use it to render some data.
text = templite.render({
'name': "Ned",
'topics': ['Python', 'Geometry', 'Juggling'],
})
这里,我们在例化的时候已经将模板传入,之后我们就可以直接对模板进行一次编译,在之后就可以通过
render
方法对模板进行多次调用。
构造函数接受一个字典参数作为内容的初始化,他们直接被存储在类内部,在后期调用
render
方法的时候可以直接引用。同样,一些会用到的函数或常量也可以在这里输入,比如之前的
upper
函数。
再开始讨论 Temlite 类实现之前,我们先来看一下这样一个类:CodeBuilder。
CodeBuilder
我们编写模板引擎的主要工作就是模板解析和产生必要的 Python 代码。为了帮助我们更好的产生 Python 代码,我们需要一个
CodeBuilder
的类,这个类主要负责代码的生成:添加代码,管理缩进以及返回最后的编译结果。
一个
CodeBuilder
实例完成一个 Python 方法的构建,虽然在我们模板引擎中只需要一个函数,但是为了更好的抽象,降低模块耦合,我们的
CodeBuilder
将不仅仅局限于生成一个函数。
虽然我们可能直到最后才会知道我们的结果是什么样子,我们还是把这部分拿到前面来说一下。
CodeBuilder
主要有两个元素,一个是用于保存代码的字符串列表,另外一个是标示当前的缩进级别。
class CodeBuilder(object):
"""Build source code conveniently."""
def __init__(self, indent=0):
self.code = []
self.indent_level = indent
下面我们来看一下我们需要的接口和具体实现。
add_line
方法将添加一个新的代码行,缩进将自动添加
def add_line(self, line):
"""Add a line of source to the code.
Indentation and new line will be added for you, don't provide them.
"""
self.code.extend([" " * self.indent_level, line, "n"])
indent
和
dedent
增加和减少缩进级别的函数:
INDENT_STEP = 4
def indent(self):
"""Increase the current indent for following lines."""
self.indent_level = self.INDENT_STEP
def dedent(self):
"""Decrease the current indent for following lines."""
self.indent_level -= self.INDENT_STEP
add_section
通过另一个 CodeBuilder 管理,这里先预留一个位置,后面再继续完善,
self
.
code
主要由代码字符列表构成,但同时也支持对其他代码块的引用。
def add_section(self):
"""Add a secton, a sub-CodeBuilder."""
section = CodeBuilder(self.indent_level)
self.code.append(section)
return section
__str__
用于产生所有代码,它将遍历
self
.
code
列表,而对于
self
.
code
中的 sections,它也会进行递归调用:
def __str__(self):
return ''.join(str(c) for c in self.code)
get_globals
通过执行代码迭代生成结果:
def get_globals(self):
"""Executer the code, and return a dict of globals if defnes."""
# A check that caller really finished all the blocks
assert self.indent_level == 0
# Get the Python source as a single string
python_source = str(self)
# Execute the source, defining globals, and return them.
global_namespace = {}
exec(python_source, global_namespace)
return global_namespace
在这里面用到了 Python 一个非常有特色的功能,
exec
函数,该函数可以将字符串作为代码执行,函数的第二个参数是一个用于收集代码定义全局变量的一个字典,比如:
python_source = """
SEVENTEEN = 17
def three():
return 3
"""
global_namespace = {}
exec(python_source, global_namespace)
print(global_namespace['SEVENTEEN'], global_namespace['three'])
输出结果:
(17, <function three at 0x029FABB0>)
[Finished in 0.1s]
虽然我们只需要 CodeBuilder 产生一个函数,但是实际 CodeBuilder 的使用并不局限于一个函数,它实际是一个更为通用的类。