Tornado模板系统实现方式
来源:互联网 发布:股票分时预警软件 编辑:程序博客网 时间:2024/06/03 12:53
最近闲来无事突然对tornado模板系统的实现方法很感兴趣,于是花了一些时间仔细的研究了tornado模板系统的实现方法。第一次阅读开源软件的代码,感受:舒心。Tornado的源代码写得很是规范和清晰,钦佩不已。
这篇文章是对Tornado模板系统源码学习的一个总结,主要包含以下几个内容:
- Tornado模板系统简单使用方法
- Tornado模板系统渲染原理
- Tornado模板系统代码实现
Tornado模板系统源码位于tornado.template这个模块里,本文中所研究的tornado版本和测试代码所采用的python版本分别为:
- tornado 4.0.1
- python 3.4
Tornado模板系统简单使用
这里不再赘述Tornado 模板系统的语法了,可以在这里找到:tornado.template — Flexible output generation
Tornado模板系统由两大组件构成:Loader
和Template
- Loader(模板加载器):负责加载和缓存模板文件
- Template(模板):负责解析、编译模板和生成输出
基本用法:
方法一:渲染字符串
t = template.Template("<html>{{ myvalue }}</html>") print(t.generate(myvalue="XXX"))
通过Template.generate()方法来生成最终输出
方法二:渲染模板文件
loader = template.Loader("/tmp") t = loader.load("test.html") print(t.generate(myvalue="XXX"))
/tmp/test.html:
<html>{{ myvalue }}</html>
通过Loader.load()加载模板文件,返回Template实例
通常,我们编写的模板不会如此简单,现在来一个稍微复杂一点的情况:假设我们有两个在/tmp/
目录下的模板文件bash.html
和 bold.html
,bold.html
extends了base.html
,我们需要渲染bold.html
,得到最终输出。
模板文件内容为:
### base.html<html> <head> <title>{% block title %}Default title{% end %}</title> </head> <body> <ul> {% for student in students %} {% block student %} <li>{{ escape(student['name']) }}</li> {% end %} {% end %} </ul> </body></html>### bold.html{% extends "base.html" %}{% block title %}A bolder title{% end %}{% block student %} <li><span style="bold">{{ escape(student['name']) }}</span></li>{% end %}
python代码实现方法:
students = [dict(name='david'), dict(name='jack')]import tornado.template as templateloader = template.Loader("/tmp")# 加载模板t = loader.load("bold.html")# 生成输出print(t.generate(students=students))
输出结果:
b'<html>\n<head>\n<title>A bolder title</title>\n</head>\n<body>\n<ul>\n\n\n<li><span style="bold">david</span></li>\n\n\n\n<li><span style="bold">jack</span></li>\n\n\n</ul>\n</body>\n</html>\n'
这样,我们得到了预期内的渲染后的html页面。
那么,在Tornado内部是如何渲染文本的呢?
Tornado模板系统渲染原理
整体来说,Tornado模板系统实现渲染的步骤很清晰:
加载模板文件之后,按照语法解析模板并将其编译成python代码,执行python代码得到渲染后的输出。
python代码?没错,Tornado将模板编译成python代码来实现模板渲染,而不是直接解析模板,嵌入数据来得到最终输出。(这一点跟JSP很相似,JSP最后是被转为java代码来实现的。)
比如前面的bold.html
,会被编译成以下代码:
def _tt_execute(): # base.html:0 _tt_buffer = [] # base.html:0 _tt_append = _tt_buffer.append # base.html:0 _tt_append(b'<html>\n<head>\n<title>') # base.html:3 _tt_append(b'A bolder title') # bold.html:3 (via base.html:3) _tt_append(b'</title>\n</head>\n<body>\n<ul>\n') # base.html:7 for student in students: # base.html:7 _tt_append(b'\n') # base.html:8 _tt_append(b'\n<li><span style="bold">') # bold.html:6 (via base.html:8) _tt_tmp = escape(student['name']) # bold.html:6 (via base.html:8) if isinstance(_tt_tmp, _tt_string_types): _tt_tmp = _tt_utf8(_tt_tmp) # bold.html:6 (via base.html:8) else: _tt_tmp = _tt_utf8(str(_tt_tmp)) # bold.html:6 (via base.html:8) _tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp)) # bold.html:6 (via base.html:8) _tt_append(_tt_tmp) # bold.html:6 (via base.html:8) _tt_append(b'</span></li>\n') # bold.html:7 (via base.html:8) _tt_append(b'\n') # base.html:11 pass # base.html:7 _tt_append(b'\n</ul>\n</body>\n</html>\n') # base.html:15 return _tt_utf8('').join(_tt_buffer) # base.html:0
执行这段代码,可以获得_tt_execute()
这个函数,通过执行这个函数就可以获得渲染后的文本了!
在继续之前,我们先仔细的研究一下生成的这段Python代码。
仔细对比_tt_execute()
这个函数和模板文件base.html
、bold.html
,会发现很有意思的规则:
- 使用_tt_buffer 列表来存放渲染后的文本片段
- 使用_tt_append(=_tt_buffer.append)来将渲染后的字符串片段添加进_tt_buffer
- 如果一段字符串既不是表达式({{ ... }}),又不是指令({% %}),就直接将这段文本_tt_append,放入_tt_buffer。(无渲染)
- 如果是模板表达式语句({{...}}),则会计算表达式结果,然后_tt_append,放入_tt_buffer。(有渲染)
如果是指令语句({% .. %}),根据指令不同,做出不同的处理。
- 如果是对模板本身进行操作的指令类型比如{% block %}, {% extends %}, {% include %}之类的,Tornado会按照指令逻辑本身对模板本身进行相应处理,不会直接生成代码来处理这种逻辑。
- 如果是可以直接转换为python代码的语句,比如 {% for %}, {% if %}, {% import .. %}, {% set .. %}之类的,则会被直接编译转为python语句。
将_tt_buffer中渲染后的字符串片段串接起来,得到最终输出。
这就是Tornado模板系统的秘密所在:按照模板语法,将模板切为一个一个的块(Chunk/Node),然后把块转换为对应逻辑的python代码,最后拼成一个整体的python代码,实现整体渲染逻辑。
比如上面那段编译后的代码是这样得到的:
编译
bold.html
的时候,发现了这样一个指令:{% extends "base.html" %}
这个时候,Tornado就知道这是继承
base.html
的一个模板,于是加载base.html
,然后替换其中的{% block title%}
和{% block student%}
,而得到了一个完整的模板:<html> <head> <title>{% block title %}A bolder title{% end %}</title> </head> <body> <ul> {% for student in students %} {% block student %}<li><span style="bold">{{ escape(student['name']) }}</span></li> {% end %} {% end %} </ul> </body> </html>
根据模板语法,会在"!!"的地方把模板切成块(Chunk/Node):
<html> <head> <title>!!{% block title %}A bolder title{% end %}!!</title> </head> <body> <ul> !!{% for student in students %}!! !!{% block student %}!!<li><span style="bold">!!{{ escape(student['name']) }}!!</span></li> {% end %} {% end %} !!</ul> </body> </html>
切开之后,就形成了一个一个的块,再把这些块整合成代码。
比如最开头的文本块:
<html> <head> <title>
被直接转换为这一段代码:
_tt_append(b'<html>\n<head>\n<title>') # base.html:3
而指令块
{% for student in students %}
,则直接被转换为python代码:for student in students: # base.html:7
表达式块
{{ escape(student['name']) }}
则会被转化成python代码:_tt_tmp = escape(student.name) # bold.html:6 (via base.html:8) if isinstance(_tt_tmp, _tt_string_types): _tt_tmp = _tt_utf8(_tt_tmp) # bold.html:6 (via base.html:8) else: _tt_tmp = _tt_utf8(str(_tt_tmp)) # bold.html:6 (via base.html:8) _tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp)) # bold.html:6 (via base.html:8) _tt_append(_tt_tmp) # bold.html:6 (via base.html:8)
其他块也是类似的原理。
最后将所有渲染后的字符串片段串接起来,形成最后输出:
return _tt_utf8('').join(_tt_buffer) # base.html:0
这些工作被Tornado分为如下步骤来完成:
解析模板内容
解析模板的目的就是把模板内容根据语法划分成一个一个的"块"。
从模板最开头开始,依次分割,将模板分解为子"块",在Tornado的实现中,将"块"叫做"结点(Node)"
结点有很多种类,比如纯文本会被转换为文本结点( _Text ),{{ *expression* }} 会被转换为表达式结点(_Expression),而像{% if *condition* %}, {% while *condition* %}之类的会被转换为控制块结点(_ContronBlock )。
每个结点都有自己的功能实现代码,比如文本结点无需渲染,那么他实现功能的代码就是:
_tt_append(TEXT)
表达式结点实现功能的代码:
_tt_tmp = EXPRESSION if isinstance(_tt_tmp, _tt_string_types): _tt_tmp = _tt_utf8(_tt_tmp) else: _tt_tmp = _tt_utf8(str(_tt_tmp)) _tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp)) _tt_append(_tt_tmp)
编译解析后的模板,得到python代码。
解析之后,就可以按顺序得到模板的各个结点了,再依次拼凑各个结点本身的实现代码,包装在 _tt_execute()这个函数里面就可以得到最终的编译后代码了。
执行编译后的python代码
- 首先执行python代码得到 _tt_execute() 这个函数。
- 执行这个函数,就可以渲染后端输出了。
Tornado模板系统代码实现
Tornado模板系统两大组件, Loader 和 Tempalte,都位于Tornado模块 tornado.template 中,其中 Loader 相对较简单,负责加载和管理模板,主要提供了三个“接口”:
class tornado.template.Loader(root_directory, **kwargs)
创建一个loader实例,可以从root_directory中加载模板文件。
load(name, parent_path=None)
加载模板文件name,返回 Template 实例
reset()
清除模板缓存
Template 则提供了两个“接口”:
class tornado.template.Template(template_string, name="<string>", loader=None, compress_whitespace=None, autoescape="xhtml_escape")
创建一个模板,template_string为需要渲染的字符串
generate(**kwargs)
渲染模板,返回渲染后文本。
Loader 部分的实现相对简单,这里不进行分析,有兴趣的同志可以在这里看实现代码:http://www.tornadoweb.org/en/stable/_modules/tornado/template.html#Loader
解析
前面提过,解析的目的在于将模板转换为一个一个的结点,每个结点都有实现自己功能的代码。
在实现中,结点抽象成基类 _Node 来表示, _Node 通过提供generate()
方法来生成该结点的python代码:
class _Node(object): def each_child(self): return () def generate(self, writer): """ 生成该Node块代码 """ raise NotImplementedError() def find_named_blocks(self, loader, named_blocks): for child in self.each_child(): child.find_named_blocks(loader, named_blocks)
其中的 each_child() 和 find_named_blocks() 这两个接口暂时先不研究。
具体的结点会继承 _Node 来提供实现自己的功能代码。例如文本节点 _Text:
class _Text(_Node): def __init__(self, value, line): """ @value: 文本内容 @line:该文本在模板中的行数 """ self.value = value self.line = line def generate(self, writer): value = self.value # Compress lots of white space to a single character. If the whitespace # breaks a line, have it continue to break a line, but just with a # single \n character if writer.compress_whitespace and "<pre>" not in value: value = re.sub(r"([\t ]+)", " ", value) value = re.sub(r"(\s*\n\s*)", "\n", value) if value: writer.write_line('_tt_append(%r)' % escape.utf8(value), self.line)
注: writer是Tornado用来生成python代码的一个工作,可以通过writer.write_line(code)来向最终代码中添加一行代码。
它的 generate() 生成代码方法最简单,如果设置为需要压缩空白的话,就将文本压缩一下,无需的话,就直接将这行代码写入总代码中去了:
'_tt_append(%r)' % escape.utf8(value)
表达式结点也是类似的原理,只不过生成代码的内容不一样:
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)
解析的工作就是从模板开头开始,根据语法寻找指令,将根据模板指令不同,生成对应的结点,大体算法:
(注:指令是指Tornado模板语法中的{{...}}, {% ... %}之类的标识着Tornado特殊语法指令)
Step 1. 生成一个body = _ChunkList
用来保存解析结果,将当前位置设为模板开头 Step 2. 从当前位置开始,往后搜寻模板指令 Step 3. 如果有下一个指令, 进入Step 4;如果没有下一个指令,进入Step 5 Step 4. 将到此为止的所有文本转换为_Text结点,append到body中。判断指令类型,将该指令转换为对应结点,append到body中。跳到Step 2 Step 5. 直接将到此为止的文本转换为_Text结点,append到body中,结束
代码:
def _parse(reader, template, in_block=None, in_loop=None): body = _ChunkList([]) while True: # Find next template directive curly = 0 while True: curly = reader.find("{", curly) if curly == -1 or curly + 1 == reader.remaining(): # EOF if in_block: raise ParseError("Missing {% end %} block for %s" % in_block) body.chunks.append(_Text(reader.consume(), reader.line)) return body # If the first curly brace is not the start of a special token, # start searching from the character after it if reader[curly + 1] not in ("{", "%", "#"): curly += 1 continue # When there are more than 2 curlies in a row, use the # innermost ones. This is useful when generating languages # like latex where curlies are also meaningful if (curly + 2 < reader.remaining() and reader[curly + 1] == '{' and reader[curly + 2] == '{'): curly += 1 continue break # Append any text before the special token if curly > 0: cons = reader.consume(curly) body.chunks.append(_Text(cons, reader.line)) start_brace = reader.consume(2) line = reader.line # Template directives may be escaped as "{{!" or "{%!". # In this case output the braces and consume the "!". # This is especially useful in conjunction with jquery templates, # which also use double braces. if reader.remaining() and reader[0] == "!": reader.consume(1) body.chunks.append(_Text(start_brace, line)) continue # Comment if start_brace == "{#": end = reader.find("#}") if end == -1: raise ParseError("Missing end expression #} on line %d" % line) contents = reader.consume(end).strip() reader.consume(2) continue # Expression if start_brace == "{{": end = reader.find("}}") if end == -1: raise ParseError("Missing end expression }} on line %d" % line) contents = reader.consume(end).strip() reader.consume(2) if not contents: raise ParseError("Empty expression on line %d" % line) body.chunks.append(_Expression(contents, line)) continue # Block assert start_brace == "{%", start_brace end = reader.find("%}") if end == -1: raise ParseError("Missing end block %} on line %d" % line) contents = reader.consume(end).strip() reader.consume(2) if not contents: raise ParseError("Empty block tag ({% %}) on line %d" % line) operator, space, suffix = contents.partition(" ") suffix = suffix.strip() # Intermediate ("else", "elif", etc) blocks intermediate_blocks = { "else": set(["if", "for", "while", "try"]), "elif": set(["if"]), "except": set(["try"]), "finally": set(["try"]), } allowed_parents = intermediate_blocks.get(operator) if allowed_parents is not None: if not in_block: raise ParseError("%s outside %s block" % (operator, allowed_parents)) if in_block not in allowed_parents: raise ParseError("%s block cannot be attached to %s block" % (operator, in_block)) body.chunks.append(_IntermediateControlBlock(contents, line)) continue # End tag elif operator == "end": if not in_block: raise ParseError("Extra {% end %} block on line %d" % line) return body elif operator in ("extends", "include", "set", "import", "from", "comment", "autoescape", "raw", "module"): if operator == "comment": continue if operator == "extends": suffix = suffix.strip('"').strip("'") if not suffix: raise ParseError("extends missing file path on line %d" % line) block = _ExtendsBlock(suffix) elif operator in ("import", "from"): if not suffix: raise ParseError("import missing statement on line %d" % line) block = _Statement(contents, line) elif operator == "include": suffix = suffix.strip('"').strip("'") if not suffix: raise ParseError("include missing file path on line %d" % line) block = _IncludeBlock(suffix, reader, line) elif operator == "set": if not suffix: raise ParseError("set missing statement on line %d" % line) block = _Statement(suffix, line) elif operator == "autoescape": fn = suffix.strip() if fn == "None": fn = None template.autoescape = fn continue elif operator == "raw": block = _Expression(suffix, line, raw=True) elif operator == "module": block = _Module(suffix, line) body.chunks.append(block) continue elif operator in ("apply", "block", "try", "if", "for", "while"): # parse inner body recursively if operator in ("for", "while"): block_body = _parse(reader, template, operator, operator) elif operator == "apply": # apply creates a nested function so syntactically it's not # in the loop. block_body = _parse(reader, template, operator, None) else: block_body = _parse(reader, template, operator, in_loop) if operator == "apply": if not suffix: raise ParseError("apply missing method name on line %d" % line) block = _ApplyBlock(suffix, line, block_body) elif operator == "block": if not suffix: raise ParseError("block missing name on line %d" % line) block = _NamedBlock(suffix, block_body, template, line) else: block = _ControlBlock(contents, line, block_body) body.chunks.append(block) continue elif operator in ("break", "continue"): if not in_loop: raise ParseError("%s outside %s block" % (operator, set(["for", "while"]))) body.chunks.append(_Statement(contents, line)) continue else: raise ParseError("unknown operator: %r" % operator)class _ChunkList(_Node): def __init__(self, chunks): self.chunks = chunks def generate(self, writer): for chunk in self.chunks: chunk.generate(writer) def each_child(self): return self.chunks
_parse() 中的reader是Tornado封装的用来操作模板的类 _TemplateReader 实例。 _TemplateReader 是类似于"资源消费者"概念的东西,这里的资源就是模板文本。你可以通过它提供的 consume(count=None) 方法来消费count个字符串:
class _TemplateReader(object): def __init__(self, name, text): self.name = name self.text = text self.line = 1 self.pos = 0 def find(self, needle, start=0, end=None): assert start >= 0, start pos = self.pos start += pos if end is None: index = self.text.find(needle, start) else: end += pos assert end >= start index = self.text.find(needle, start, end) if index != -1: index -= pos return index def consume(self, count=None): if count is None: count = len(self.text) - self.pos newpos = self.pos + count self.line += self.text.count("\n", self.pos, newpos) s = self.text[self.pos:newpos] self.pos = newpos return s def remaining(self): return len(self.text) - self.pos def __len__(self): return self.remaining() def __getitem__(self, key): if type(key) is slice: size = len(self) start, stop, step = key.indices(size) if start is None: start = self.pos else: start += self.pos if stop is not None: stop += self.pos return self.text[slice(start, stop, step)] elif key < 0: return self.text[key] else: return self.text[self.pos + key] def __str__(self): return self.text[self.pos:]
这是一个很优美的存在 :)
编译
如果读者你仔细看了上文,并且我说明白了你看懂了的话,你可能会觉得编译应该是个很简单的事情:各个结点都可以生成自己的代码了,直接组合一下就Ok了。
有一个特殊的情况,就是 {% extends *filename* %} 这个指令,这个指令的功能:
该指令所在的模板继承自filename, 用该模板中的{% block ...%} 标签内容去替换父模板filename中的相应{% block %}内容来达到扩展的目的。该模块中除了{% block %}标签内容以外,其他的内容都会被忽略。
所以,当Tornado编译一个模板时,如果在开头遇到了这个标签,必须得先加载父模板,然后替换这些{% block %} 标签块。假如父模板又用到了父模板(祖先模板)怎么办?根据规则,以最祖先的模板为基本模板,用他的孩子、孙子、曾孙...中的{% block %}标签块内容替换原来的{% block %}块。
在Tornado实现时,{% block %}标签块结点用 _NamedBlock类来表示,具体代码:
class Template(object): ... def _generate_python(self, loader, compress_whitespace): buffer = StringIO() try: # named_blocks maps from names to _NamedBlock objects named_blocks = {} # 获取该模板继承(extends)的一些模板, ancestor是_File的实例 ancestors = self._get_ancestors(loader) ancestors.reverse() # 按照继承链从祖先往子孙找出所有的{% block %}块(_NamedBlock),存放至named_block中 for ancestor in ancestors: ancestor.find_named_blocks(loader, named_blocks) writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template, compress_whitespace) # 最老祖先generate ancestors[0].generate(writer) return buffer.getvalue() finally: buffer.close() def _get_ancestors(self, loader): ancestors = [self.file] for chunk in self.file.body.chunks: if isinstance(chunk, _ExtendsBlock): if not loader: raise ParseError("{% extends %} block found, but no " "template loader") template = loader.load(chunk.name, self.name) ancestors.extend(template._get_ancestors(loader)) return ancestors
其中的 self.file 就是存放各个结点的模板容器,它存放了所有的结点,并且将各个结点的代码串接起来,包装在函数_tt_execute() 中得到最终生成的python代码:
class _File(_Node): """模板文件,最后生成代码""" def __init__(self, template, body): self.template = template # an instance of _ChunkList self.body = body self.line = 0 def generate(self, writer): """ _tt_execute(): 运行时渲染的函数 _tt_buffer=[]: 单Node渲染后的结果字符串列表 _tt_append = _tt_buffer.append """ writer.write_line("def _tt_execute():", self.line) with writer.indent(): writer.write_line("_tt_buffer = []", self.line) writer.write_line("_tt_append = _tt_buffer.append", self.line) self.body.generate(writer) writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line) def each_child(self): return (self.body,)
(其中的self.body就是 _parse 的结果)
至此,Python代码终于编译好了。
解析和编译这两个步骤,全部都是在生成 Template 的时候完成的:
class Template(object): ... def __init__(self, template_string, name="<string>", loader=None, compress_whitespace=None, autoescape=_UNSET): reader = _TemplateReader(name, escape.native_str(template_string)) # 解析模板 self.file = _File(self, _parse(reader, self)) # 生成的python代码 self.code = self._generate_python(loader, compress_whitespace) self.loader = loader try: # Under python2.5, the fake filename used here must match # the module name used in __name__ below. # The dont_inherit flag prevents template.py's future imports # from being applied to the generated code. self.compiled = compile( escape.to_unicode(self.code), "%s.generated.py" % self.name.replace('.', '_'), "exec", dont_inherit=True) except Exception: formatted_code = _format_code(self.code).rstrip() app_log.error("%s code:\n%s", self.name, formatted_code) raise
接下来,通过调用 Template.generate(kwargs) 就可以获得最终渲染后的输出了。
执行python代码
这段直接来上代码就好了:
class Template(object): ... def generate(self, **kwargs): """Generate this template with the given arguments.""" namespace = { "escape": escape.xhtml_escape, "xhtml_escape": escape.xhtml_escape, "url_escape": escape.url_escape, "json_encode": escape.json_encode, "squeeze": escape.squeeze, "linkify": escape.linkify, "datetime": datetime, "_tt_utf8": escape.utf8, # for internal use "_tt_string_types": (unicode_type, bytes_type), # __name__ and __loader__ allow the traceback mechanism to find # the generated source code. "__name__": self.name.replace('.', '_'), "__loader__": ObjectDict(get_source=lambda name: self.code), } namespace.update(self.namespace) namespace.update(kwargs) exec_in(self.compiled, namespace) execute = namespace["_tt_execute"] # Clear the traceback module's cache of source data now that # we've generated a new template (mainly for this module's # unittests, where different tests reuse the same name). linecache.clearcache() return execute()
我第一次看到这里的时候,眼前一亮,我终于知道Tornado模板中那些内置的函数是什么原理了!看这一句:
exec_in(self.compiled, namespace)
exec_in是什么!?
它是Tornado包装Python exec 语句的一个函数:
def exec_in(code, glob, loc=None): if isinstance(code, str): code = compile(code, '<string>', 'exec', dont_inherit=True) exec(code, glob, loc)
亮点来了!exec(code, glob, loc)这个函数的魔法:
在程序内执行这段code代码,使用glob作为它的全局变量容器,loc作为它的本地变量容器! 也就是说,你可以给这段代码制定运行前的变量(glob),同时可以获得他的运行结果(loc)! 当loc = None 时,执行code之后生成的变量会直接保存在glob中。
那么回头看 Template.generate(\kwargs) 代码,我们可以通过给 exec_in 这个函数传递全局名字空间而实现"内置变量"的感觉!像Tornado模板内置的 escape / linkify 函数,还有在 RequestHandler 中的 render()* 方法中传递的模板参数, 都是这个时候传进去的!
神奇的魔法,Python居然还可以这么用,我第一次看见这种用法的时候这样想。
总结 & 阅读感想
Tornado是我读过的第一个开源软件,是一个特别棒的软件。他的棒不仅仅是在代码注释、代码风格以及各种技术的恰当的使用上,更在它的整个软件的设计上。很多的设计,我觉得很美妙,可惜笨拙如我,没法用文字描述出来 TAT。
Tornado源码里边,我特别想提的两个地方: 代码规范简洁 & 设计
代码规范
- 内部使用的类、方法一定会以单个下划线 "_" 开头,这样很方便的区分内部类和方法而快速定位。
- 这些内部使用的类一定会放在文件的末尾,方法一定会放在类的末尾,很符合阅读时候的从上而下的逻辑。
设计
设计上:解耦:
- 将一个模板的各个部分用结点(_Node)来分开,而不是直接处理各个部分的逻辑,拼接各个部分的输出。好处是每个结点的逻辑清晰,当后期扩展指令/修改指令时不会整体的代码,只会影响对应结点。
- 将遍历模板字符串的过程抽象出来,用“消费资源”的概念,将其抽象成 _TemplateReader ,这样 _parse 这段解析的代码就可以将注意力集中在解析的逻辑本身,而不必关心字符串处理相关的细节。
- 将生成代码逻辑抽象出来,形成 _CodeWriter ,好处是在生成代码的时候,也可以更好的将注意力集中在逻辑本身,而无需在乎这些基础服务。
当一部分的逻辑过于复杂的时候,如何剥离代码逻辑、如何抽象出实体,对我来说,是一个很难的话题。但是在这里,可以看到Tornado做得特别好,值得学习和思考。
PS.
第一次写这种分析源码的文章。写完才发现,很多时候,看这种分析文章还不如直接去看代码来得快,这样的文章是否有价值?
其他发现
threading: a hign lever threading interface
threading.RLock: 一个 可重入锁(reentrant lock)生成器,和一般的锁不同,允许一个线程递归多次获取锁,但是必须释放同样次数才能被其他线程使用。避免单线程递归使用锁时进入死锁状态。
(写于2014-12-30,最近编辑于2015-02-13)
- Tornado模板系统实现方式
- tornado模板
- Tornado自带的模板系统入门示例
- Tornado模板机制分析
- 学习tornado:模板
- tornado模板引擎原理
- tornado 模板总结
- tornado入门 - 模板
- tornado系列二:模板
- tornado模板引擎原理
- tornado 5、模板扩展
- tornado 4、模板使用
- tornado模板参数传递
- tornado模板引擎原理
- python tornado 模板扩展
- Tornado框架06-模板
- python tornado模板使用
- jquery 模板实现方式
- 当MongoDB遇上Spring
- java.sql.exception:before start of result set
- 调用Windows属性窗口
- DML、DDL、DCL区别 .
- 链表的基本操作C/c++
- Tornado模板系统实现方式
- 基于python实时监控cpu的小工具
- Oracle中几个需要用动态语句执行的情况
- linux shell中"2>&1"含义
- 对Android view/viewgroup事件分发的理解
- 压缩字节数组
- VC6程序如何跟踪进入DLL函数调试
- iOS webview清除缓存
- tornado模板引擎原理