前两篇文章主要说明了web.py的模板系统将模板文件处理后获得的结果:__template__()
函数。本文主要讲述模板文件是如何变成__template__()
函数的。html
通常来讲,更经常使用的是Render类,该类会处理整个目录下的模板,还支持缓存和嵌套模板。不过这些其实都和模板自己的解析基本不要紧,之后再说明这个类的实现和用途。这里咱们使用frender()
函数:node
def frender(path, **keywords): """Creates a template from the given file path. """ return Template(open(path).read(), filename=path, **keywords)
这个函数至关简单,只做了一键事情,就是读取模板文件内容,而后交给Template类处理,而且返回一个Template类实例。从这里也能够看出,整个模板的解析,只和Template类有关,frender是来打杂的。python
当咱们根据一个模板内容建立一个Template类实例t后,咱们能够调用该实例,这至关于调用模板对应的__template__()
函数,获得的结果是一个TemplateResult实例。web
In [7]: t = web.template.frender("templates/hello.html") # coding: utf-8 def __template__ (name): __lineoffset__ = -4 loop = ForLoop() self = TemplateResult(); extend_ = self.extend extend_([u'hello, ', escape_(name, True), u'\n']) return self In [8]: print t("xxxx") hello, xxxx In [9]: print type(t("xxxx")) <class 'web.template.TemplateResult'>
Template实例化过程是把模板转换成HTML内容的实质性步骤,不过这个过程比较复杂。可是,归纳的来说,这个过程和Template的__init__()
函数中的步骤差不都差很少。express
# Template类的__init__()函数 def __init__(self, text, filename='<template>', filter=None, globals=None, builtins=None, extensions=None): self.extensions = extensions or [] text = Template.normalize_text(text) code = self.compile_template(text, filename) _, ext = os.path.splitext(filename) filter = filter or self.FILTERS.get(ext, None) self.content_type = self.CONTENT_TYPES.get(ext, None) if globals is None: globals = self.globals if builtins is None: builtins = TEMPLATE_BUILTINS BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)
首先把,参数里除了text之外的参数忽略掉,而后来看下对text的处理过程(text就是模板的内容)。整个过程归纳的说有以下步骤:缓存
对text做归一化处理:text = Template.normalize_text(text)
, 主要换行符统一成\n,删除BOM字符串,将\$替换成$$,这个就是简单的字符串处理。框架
编译模板获得编译后的Python字节码code:code = self.compile_template(text, filename)
,code就是以前已经提到过的__template__()
函数。函数
调用父类(BaseTemplate)的__init__()
函数:建立__template__()
函数的执行环境,而且实现可调用功能。oop
其余没有说明的代码,暂时均可以忽略,不会影响你理解Template的实例化过程。从上面的步骤能够看出,Template实例化的过程主要有两个:生成__template__()
函数的代码并编译,以及建立__template__()
函数的执行环境。ui
这个是模板生成过程当中最长最复杂的一段,会应用到Python的token分析功能以及动态编译功能。还记得第一篇里,咱们搭建实验环境的时候,修改了web.py源码,在一个地方插入了一行打印语句么?没错,就是这个compile_template()
函数,咱们如今来看看它是如何生成__template__()
函数的代码的。
def compile_template(self, template_string, filename): code = Template.generate_code(template_string, filename, parser=self.create_parser()) def get_source_line(filename, lineno): try: lines = open(filename).read().splitlines() return lines[lineno] except: return None print code # 上次增长的打印语句 ...
很明显,第一行的调用就生成了__template__()
函数的代码。咱们继续看:
def generate_code(text, filename, parser=None): # parse the text parser = parser or Parser() rootnode = parser.parse(text, filename) # generate python code from the parse tree code = rootnode.emit(indent="").strip() return safestr(code) generate_code = staticmethod(generate_code) def create_parser(self): p = Parser() for ext in self.extensions: p = ext(p) return p
这两个函数配合起来使用,意思就是:建立一个没有扩展的Parser实例,用该实例解析模板内容获得rootnode,调用rootnode的emit()
函数获得最终的代码(__template__()
函数)。因此,问题的关键由转到了Parser类上。
Parser类的parse函数解析模板内容,而后返回一些节点结构,这种节点结构的emit方法会产生实际的Python代码。
class Parser: """Parser Base. """ def __init__(self): self.statement_nodes = STATEMENT_NODES self.keywords = KEYWORDS def parse(self, text, name="<template>"): self.text = text self.name = name defwith, text = self.read_defwith(text) suite = self.read_suite(text) return DefwithNode(defwith, suite) ...
上面的代码是Template类中用的Parser类的两个方法,初始化函数没啥好说的,parse函数则是说明了模板解析的最顶层逻辑:
先解析def with
这行。
而后解析剩余部分。
最后返回一个DefwithNode实例。
这里不打算细说整个解析过程(后面专门说),反正知道了Parser类的parse函数能够返回一个DefwithNode实例,且调用其emit()
方法后能生成最终的代码便可。
在compile_template
方法获得模板对应的代码后,就要对这个代码进行编译,生成Python的字节码:
def compile_template(self, template_string, filename): code = Template.generate_code(template_string, filename, parser=self.create_parser()) ... print code try: # compile the code first to report the errors, if any, with the filename compiled_code = compile(code, filename, 'exec') except SyntaxError, e: # display template line that caused the error along with the traceback. ... return compiled_code
到此就完成了Template类实例化过程的第二个步骤:获得了编译后的模板函数代码。
接下来就是要调用父类,也就是BaseTemplate类,的初始化方法了:
BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)
该方法接受的参数中,code就是上面编译出来的字节码,globals和builtins是用来构建__template__()
函数的运行环境的,记得在web.py的文档中有说能够修改这两个的地方么?忘记的话,传送门。filter函数是过滤函数,用来处理生成的HTML内容。
若是你的代码中不指定,默认状况下,globals是空的,builtins则包含以下内容:
TEMPLATE_BUILTIN_NAMES = [ "dict", "enumerate", "float", "int", "bool", "list", "long", "reversed", "set", "slice", "tuple", "xrange", "abs", "all", "any", "callable", "chr", "cmp", "divmod", "filter", "hex", "id", "isinstance", "iter", "len", "max", "min", "oct", "ord", "pow", "range", "True", "False", "None", "__import__", # some c-libraries like datetime requires __import__ to present in the namespace ]
这些就是Python的全局内置函数的一个子集,你在模板中能够直接使用。
这个初始化过程,咱们直接看代码(只列出相关函数):
class BaseTemplate: def __init__(self, code, filename, filter, globals, builtins): self.filename = filename self.filter = filter self._globals = globals self._builtins = builtins if code: self.t = self._compile(code) else: self.t = lambda: '' def _compile(self, code): env = self.make_env(self._globals or {}, self._builtins) exec(code, env) return env['__template__'] def make_env(self, globals, builtins): return dict(globals, __builtins__=builtins, ForLoop=ForLoop, TemplateResult=TemplateResult, escape_=self._escape, join_=self._join )
初始化的时候,主要的工做是调用self.t = self._compile(code)
。_compile()
方法先使用make_env()
方法构造一个函数运行环境env,里面包含了__template__()
函数会用到的对象,包括默认的内置函数,以及TemplateResult等。而后,调用exec(code, env)
在env环境下执行__template__()
函数的定义,最后返回的env['__template']
就是在env环境下执行的__template__()
函数。
注意:*在python2中,exec是一个语句:
The first expression should evaluate to either a Unicode string, a Latin-1 encoded string, an open file object, a code object, or a tuple.
exec还有下面这种形式:
exec code in globals, locals
若是以元组做为参数,则有以下两个形式:
exec (code, globals) exec (code, globals, locals)
globals字典中能够插入__builtins__ 对象来设置内置对象的引用,好比上面的make_env()
函数。
如今能够来看Parser类是如何解析模板,并生成特定的节点结构的。仍是从parse函数开始:
def parse(self, text, name="<template>"): self.text = text self.name = name defwith, text = self.read_defwith(text) suite = self.read_suite(text) return DefwithNode(defwith, suite)
首先调用read_defwith()
方法,将$def with (name)
这行分离出来,剩余的都交给read_suite()
方法去解析,最后再实例化一个DefwithNode做为解析结果。整个解析的主要工做是由read_suite()
方法完成的。
Parser类里面实现了以下这些方法:
def read_assignment : function def read_block_section : function def read_expr : function def read_indented_block : function def read_keyword : function def read_node : function def read_section : function def read_statement : function def read_suite : function def read_text : function def read_var : function def readline : function
这些方法的参数和返回值都遵循同一个模式,先知道一下有助于阅读代码。
每一个方法都负责解析特定的内容(方法名能够体现),有的方法内部会调用其余的方法。
参数都是一个text,表示还未解析的模板内容。
返回值都是含有两个元素的元组,第一个元素是该函数解析的结果(字符串或者某个类实例),第二个元素是该函数处理事后还剩余的模板内容(给下个函数去解析)。
从这个惯例能够看出,整个模板解析的思想是:从模板内容头部开始,一点一点的读取,一旦所读取的内容能够解析就进行解析,剩余的内容再继续这么处理。
另外,这些解析函数还有包含关系,有一些是处理整块内容的函数,有一些则是处理一行,还有的是处理一个单词,这也就造成了一个调用关系:粗粒度的函数调用细粒度的解析函数。以下图所示:
上一小节提到了,解析函数返回值中的第一个通常都是解析好的节点类实例。那么什么是解析节点呢?解析节点其实就是一些类,这些类都实现了emit()
函数,当调用emit()
时,就会产生对应的代码字符串。先来看下有哪些解析节点吧:
-- TextNode ExpressoinNode LineNode VarNode StatementNode AssignmentNode BlockNode IfNode, ElseNode, ElifNode ForNode CodeNode DefNode SuiteNode DefwithNode
这些节点的功能基本上从类名称就能够看出来,有些节点只是其余节点的包装(好比LineNode),有一些则须要处理比较复杂的状况(好比ForNode)。可是这些节点做的事情都是能够归纳为:处理初始化参数以便在调用emit方法的时候能产生正确的代码。来看两个例子:
模板内容
$def with (name) $ b = name
函数内容
def __template__ (name): __lineoffset__ = -4 loop = ForLoop() self = TemplateResult(); extend_ = self.extend b = name return self
咱们知道当解析到$ b = name
这行时,会生成一个AssignmentNode,调用read_assignment()
的代码在Parser类的read_section()
方法里,咱们能够模拟一下:
In [16]: node, _ = p.read_assignment(" b = name\n") In [18]: print node <assignment: 'b = name'> In [20]: print node.emit(" ") b = name
web.py的模板系统就是这样针对每一个节点调用emit()
方法来生产最终的代码的。
咱们再来看复杂一点的ForNode节点。
模板内容
$def with (name_list) $for name in name_list: $name
函数内容
def __template__ (name_list): __lineoffset__ = -4 loop = ForLoop() self = TemplateResult(); extend_ = self.extend for name in loop.setup(name_list): extend_([escape_(name, True), u'\n']) return self
web.py中调用建立ForNode实例的方法是read_block_section()
,咱们能够这么模拟:
In [27]: node = web.template.ForNode("for name in name_list\n", " $name\n") In [28]: print node <block: 'for name in name_list\n', [<line: [t' ', $name, t'\n']>]> In [31]: print node.emit(" ") for name in loop.setup(name_list): extend_([u' ', escape_(name, True), u'\n'])
建立ForNode节点实例时,是把第一行的循环控制语句和其余的循环内部代码分别做为两个参数传递给ForNode的初始化函数。ForNode的主要工做是解析第一个参数,转换为loop.setup()
这样的代码(为了可以在模板中支持loop关键字)。而后,调用父类BlockNode的初始化函数,主要做用是调用Parser类的read_suite()
方法解析循环内部的代码,由于咱们在循环内部也会使用模板语法(就像上面例子中的$name\n
被转换成extend_([u' ', escape_(name, True), u'\n'])
)。
class BlockNode: def __init__(self, stmt, block, begin_indent=''): self.stmt = stmt self.suite = Parser().read_suite(block) self.begin_indent = begin_indent def emit(self, indent, text_indent=''): text_indent = self.begin_indent + text_indent out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent) return out def __repr__(self): return "<block: %s, %s>" % (repr(self.stmt), repr(self.suite)) class ForNode(BlockNode): def __init__(self, stmt, block, begin_indent=''): self.original_stmt = stmt tok = PythonTokenizer(stmt) tok.consume_till('in') a = stmt[:tok.index] # for i in b = stmt[tok.index:-1] # rest of for stmt excluding : stmt = a + ' loop.setup(' + b.strip() + '):' BlockNode.__init__(self, stmt, block, begin_indent) def __repr__(self): return "<block: %s, %s>" % (repr(self.original_stmt), repr(self.suite))
SuiteNode内部就是一个列表,保存了全部子节点,调用SuiteNode的emit()
方法时,就是依次调用子节点的emit()
方法,而后链接成一个字符串:
def emit(self, indent, text_indent=''): return "\n" + "".join([s.emit(indent, text_indent) for s in self.sections])
DefwithNode做为根节点,作了两个事情:
生成__template__()
函数的框架。
调用suite.emit()
生成其他的代码,而且拼接获得一个完整的函数。
本章讲解了Parser类的大部分实现,可是没有讲解Parser类如何分析模板内容来肯定生成哪一个节点。这部分的内容都是具体的实现细节,采用的分析技术主要有两种:
字符串分析,看看字符串是否以某些特定模式开始。
token分析,利用了Python的tokenize模块的功能来分析。
本文主要是分析了从模板文件到Template类实例的生成过程,大概是以下几个步骤:
调用frender()
函数读取模板文件内容,做为参数传递给Template类的初始化函数。
Template类调用Parser类将模板内容解析成__template__()
函数的定义代码。
Template类调用Python的compile函数将生成的__template__()
定义代码进行编译。
Template类调用父类BaseTemplate类的初始化函数构建__template__()
函数的执行环境,获得在指定环境下执行的__template__()
函数。