template中的_parse方法是模板文法的解析器,而这个文件中一坨一坨的各种node以及block,就是解析结果的承载者,也就是说在经过parse处理过后,我们输入的tornado的html模板就变成了各种block的集合。
这些block和node的祖宗就是这个“抽象”类, _Node,它定义了三个方法定义,其中generate方法是必须由子类提供实现的(所以我叫它“抽象”类)。
理论上来说,当一个类成为祖宗类时,必定意味着这个类包含了一些在子类中通用的行为,那么,从_Node暴露出来的方法来看,即所有的子类理论上都会有如下特征:
1. 可作为容器 (each_child, find_named_blocks)
2. generate
当然了,理想总是丰满的,现实也总有那么点儿不对劲,对于某些子孙,它们的特征看上去不是那么靠谱,比如_Text。
_Text这个类只用到了generate这个方法,用于将文字(Html, JS)经过trim后添加到输入流中,如果调用它的each_child or find_named_blocks,当然你能这么做,但是没有什么意义。
前面反复说到_Parse方法,它返回的结果是一个_ChunkList的实例,而_ChunkList继承与_Node。这是一个体现了_Node容器特点的类,重写了generate方法和each_child方法,而基本上就是依次调用容器内所有元素的相关方法而已。
_Nodes众多子子孙孙中比较奇葩的是_ExtendsBlock这个类,丫什么事情都没做(That is true),看上去像是另外一个“抽象类”,但是居然会被_Parse初始化,用于处理Extends这个token(tornado术语)。我就纳闷了,一旦这货被generate,难道不会抛一个异常出来木?
真正有意思的是另外几个方法,它们有共通的模式,用_ApplyBlock来举例
在_ApplyBlock中,有趣的是generate方法
def generate(self, writer):
method_name = "apply%d" % writer.apply_counter
writer.apply_counter += 1
writer.write_line("def %s():" % method_name, self.line)
with writer.indent():
writer.write_line("_buffer = []", self.line)
writer.write_line("_append = _buffer.append", self.line)
self.body.generate(writer)
writer.write_line("return _utf8('').join(_buffer)", self.line)
writer.write_line("_append(%s(%s()))" % (
self.method, method_name), self.line)
</div>
简单来说,这个函数做了两件事情:
定义了一个python文件全局函数叫做applyXXX():,其中的XXX是一个整形的,自增的值,返回值是一个utf8字符串。
执行这个applyXXX函数,将此函数的输出再作为self.method这个函数的输入。
所以,如果一个类似于这样的模板
{%apply linkify%} {{address}} {%end%}
</div>
会得到一个类似于如下的输出:
r = applyXXX() r = linkify(r) _append(r)</div>
tornado的template机制,本质上讲,就是允许开发者已HTML + template marker的方式来编写视图模板,但是在背后,tornado会把这些视图模板通过template的处理,变成可编译的python代码。
拿autumn-sea上面的代码作为例子,比较容易理解:
View Template
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
hello! {{ name }}
</body>
</html>
</div>
处理后
_buffer = []
_buffer.append('<html>\\n<head>\\n<title>')
_tmp = title
if isinstance(_tmp, str): _buffer.append(_tmp)
elif isinstance(_tmp, unicode): _buffer.append(_tmp.encode('utf-8'))
else: _buffer.append(str(_tmp))
_buffer.append('</title>\\n</head>\\n<body>\\n')
_buffer.append('hello! ')
_tmp = name
if isinstance(_tmp, str): _buffer.append(_tmp)
elif isinstance(_tmp, unicode): _buffer.append(_tmp.encode('utf-8'))
else: _buffer.append(str(_tmp))
_buffer.append('\\n</body>\\n</html>\\n')
return ''.join(_buffer)\n"
</div>
实例剖析
tornado的模板基本都在template.py这个文件中,短短800多行代码就实现了基本可用的模板,让我们慢慢揭开她的面纱。
首先我们看看tornado是如何编译模板的,下面是个简单的模板
t = Template("""\
{%if names%}
{% for name in names %}
{{name}}
{%end%}
{%else%}
no one
{%end%}
""")
</div>
tornado最后编译代码如下:
def _tt_execute(): # <string>:0
_tt_buffer = [] # <string>:0
_tt_append = _tt_buffer.append # <string>:0
if names: # <string>:1
_tt_append('\n ') # <string>:2
for name in names: # <string>:2
_tt_append('\n ') # <string>:3
_tt_tmp = name # <string>:3
if isinstance(_tt_tmp, _tt_string_types): _tt_tmp = _tt_utf8(_tt_tmp) # <string>:3
else: _tt_tmp = _tt_utf8(str(_tt_tmp)) # <string>:3
_tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp)) # <string>:3
_tt_append(_tt_tmp) # <string>:3
_tt_append('\n ') # <string>:4
pass # <string>:2
_tt_append('\n') # <string>:5
pass # <string>:5
else: # <string>:5
_tt_append('\nno one\n') # <string>:7
pass # <string>:1
_tt_append('\n') # <string>:8
return _tt_utf8('').join(_tt_buffer) # <string>:0
</div>
是的,你没看错,tornado编译就是将之翻译成一个个代码块,最后通exec传递我们给的参数命名空间执行_tt_execute函数。
在我们上面的模板中包含了4种预定义的NODE节点,_ControlBlock,_Expression,_TEXT,每种Node节点都有自己的生成方式。
比如说_Expression表达式节点,也就是我们模板中的{{name}},当_parse解析时发现'{'后面还是'{'就认为是表达式节点,
class _Expression(_Node):
def __init__(self, expression, line, raw=False):
self.expression = expression
self.line = line
self.raw = raw
def generate(self, writer):
writer.write_line("_tt_tmp = %s" % self.expression, self.line)
writer.write_line("if isinstance(_tt_tmp, _tt_string_types):"
" _tt_tmp = _tt_utf8(_tt_tmp)", self.line)
writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line)
if not self.raw and writer.current_template.autoescape is not None:
# In python3 functions like xhtml_escape return unicode,
# so we have to convert to utf8 again.
writer.write_line("_tt_tmp = _tt_utf8(%s(_tt_tmp))" %
writer.current_template.autoescape, self.line)
writer.write_line("_tt_append(_tt_tmp)", self.line)
</div>
最后生成时会调用节点的generate方法,self.expression就是上面的name,所以当exec的时候就会把name的值append到内部的列表中。
像if,for等都是控制节点,他们的定义如下:
class _ControlBlock(_Node):
def __init__(self, statement, line, body=None):
self.statement = statement
self.line = line
self.body = body
def each_child(self):
return (self.body,)
def generate(self, writer):
writer.write_line("%s:" % self.statement, self.line)
with writer.indent():
self.body.generate(writer)
# Just in case the body was empty
writer.write_line("pass", self.line)
</div>
控制节点的generate方法有点意义,因为if,for等是下一行是需要缩进的,所以调用了with writer.indent继续缩进控制,可以看下
_CodeWriter的indent方法。
节点中比较有意思的是_ExtendsBlock,这是实现目标基础的节点,
class _ExtendsBlock(_Node):
def __init__(self, name):
self.name = name
</div>
我们发现并没有定义generate方法,那当生成继承节点时不是会报错吗?让我们看一段事例
loader = Loader('.')
t=Template("""\
{% extends base.html %}
{% block login_name %}hello world! {{ name }}{% en
您可能想查找下面的文章:
- python用装饰器自动注册Tornado路由详解
- 使用Python的Tornado框架实现一个Web端图书展示页面
- 深入解析Python的Tornado框架中内置的模板引擎
- 使用Python的Tornado框架实现一个Web端图书展示页面
- 为Python的Tornado框架配置使用Jinja2模板引擎的方法
- Python的Tornado框架实现异步非阻塞访问数据库的示例
- Python的Tornado框架实现图片上传及图片大小修改功能
- Python的Tornado框架的异步任务与AsyncHTTPClient
- 剖析Python的Tornado框架中session支持的实现代码
- 在Python的Tornado框架中实现简单的在线代理的教程

