二、命名Block
1. 命令Block的使用
相对匿名block,命名block的用途更广泛,不过命名block也有一些特有的属性,我们先来看看一段常用的代码:
<html>
<%block name="header">
<head>
<title>
<%block name="title">Title</%block>
</title>
</head>
</%block>
<body>
</body>
</html>
这段代码中定义两个block,其中title内嵌在header中。不过在mako中,尽管他们的定义是有内嵌关系的,生成的block也是全局的。也就是说只要是命名block,在模板里面都是可以全局引用的。上面模板生成的代码如下:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
def header(): # 所有的block都是全局的,所以所有block都会在这里定义函数
return render_header(context.locals_(__M_locals))
def title(): # 所有的block都是全局的,所以所有block都会在这里定义函数
return render_title(context.locals_(__M_locals))
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'\n\n')
if 'parent' not in context._data or not hasattr(context._data['parent'], 'header'):
context['self'].header(**pageargs) # 如果有继承关系的话,提取最顶层的模板(self),然后调用其中的header block
# SOURCE LINE 9
__M_writer(u'\n\n\n
\n') return ''
finally:
context.caller_stack._pop_frame()
def render_header(context,**pageargs):
… ...
def render_title(context,**pageargs):
… ...
2. 重复调用Block
一个block定义之后,可以在模板里面多次引用,引用方式跟函数调用一样。如:
<div name="page">
<%block name="pagecontrol">
<a href="">previous page
| <a href="">next page
</%block>
<table>
## some content
</table>
${pagecontrol()}
</div>
这段代码将在table上下都生成一个pagecontrol。
通过上面几个例子我们可以看到:
* Block定义是全局的,所以Block不能重名,即便是内嵌的Block也不能重名
* Block不像函数一样可以带参数
* Block 不能在def中、<% call>、<%namespacename:defname>等地方使用
Friday, December 20, 2013
Thursday, December 12, 2013
mako二三事 -- Blocks一
除了函数,mako还提供了一种特殊的结构 — Block。在很大程度上,Block跟Def非常相似,但作为模板,需要构建灵活多变的页面结构,Block又有着一些Def所不具备的特殊特性。
一、Block的使用
block跟def最大的一个区别就是def在定义之后需要专门去调用它才会生效,block不需要,block定义之后就会默认在该定义处生效渲染。正因为这种特性,block就可以匿名使用。例如:
<html>
<body>
<%block>
this is a block.
</%block>
</body>
</html>
将会生成
<html>
<body>
this is a block.
</body>
</html>
该block的定义如果使用def来表达的话,等同于:
<html>
<body>
<%def name="testblock()">
this is a block.
</%def>${testblock()}
</body>
</html>
尽管在用法上与def有所不同,def上的一些比较有用的参数还是可以在block上使用的,如:
<html>
<body>
<%block filter="h">
this is some escaped html.
</%block>
</body>
</html>
<html>
<body>
<%block cached="True" cache_timeout="60">
This content will be cached for 60 seconds.
</%block>
</body>
</html>
% for i in range(1, 4):
<%block>i is ${i}</%block>
% endfor
我们再来看一下第一段block模板生成的代码:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
def __M_anon_4(): # 跟def类似,block块在python中生成的代码也是一个内嵌函数
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 4
__M_writer(u'\n this is a block.\n ')
return ''
finally:
context.caller_stack._pop_frame()
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'\n\n \n ')
__M_anon_4() # 调用block
# SOURCE LINE 6
__M_writer(u'\n
\n\n') return ''
finally:
context.caller_stack._pop_frame()
因为这里使用了匿名block,所以名字为anon加一个递增数字。同样的,匿名函数不支持其他地方调用,所哟没有生成一个render_XXX函数。
一、Block的使用
block跟def最大的一个区别就是def在定义之后需要专门去调用它才会生效,block不需要,block定义之后就会默认在该定义处生效渲染。正因为这种特性,block就可以匿名使用。例如:
<html>
<body>
<%block>
this is a block.
</%block>
</body>
</html>
将会生成
<html>
<body>
this is a block.
</body>
</html>
该block的定义如果使用def来表达的话,等同于:
<html>
<body>
<%def name="testblock()">
this is a block.
</%def>${testblock()}
</body>
</html>
尽管在用法上与def有所不同,def上的一些比较有用的参数还是可以在block上使用的,如:
<html>
<body>
<%block filter="h">
this is some escaped html.
</%block>
</body>
</html>
<html>
<body>
<%block cached="True" cache_timeout="60">
This content will be cached for 60 seconds.
</%block>
</body>
</html>
% for i in range(1, 4):
<%block>i is ${i}</%block>
% endfor
我们再来看一下第一段block模板生成的代码:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
def __M_anon_4(): # 跟def类似,block块在python中生成的代码也是一个内嵌函数
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 4
__M_writer(u'\n this is a block.\n ')
return ''
finally:
context.caller_stack._pop_frame()
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'\n\n \n ')
__M_anon_4() # 调用block
# SOURCE LINE 6
__M_writer(u'\n
\n\n') return ''
finally:
context.caller_stack._pop_frame()
因为这里使用了匿名block,所以名字为anon加一个递增数字。同样的,匿名函数不支持其他地方调用,所哟没有生成一个render_XXX函数。
Tuesday, December 10, 2013
mako二三事 -- 函数四
九、def的其他的调用方式
mako中的函数不仅仅是函数,还有一种模板特有的调用方式。我们经常使用它来创建自定义标签:
1. 自定义标签
<%def name="buildtable()">
${caller.body()}
</%def>
<%self:buildtable>
I am the table body.
</%self:buildtable>
输出:
I am the table body.
self:buildtable 这种用法是mako0.2.3之后引入的一种用法,通过这种方式,可以创建任意的定制标签。在这段代码中,需要注意的是:
a. ${caller.body()}表示读取调用者的body部分内容,这里就是 I am the table body.
b. self为namespace,这里的self表示本模块。也可以是其他包含所需函数的模块名
2. 带参数的标签
函数可以带参数执行,做成自定义标签之后,也一样可以带入需要的参数,如:
<%def name="lister(count)">
% for x in range(count):
${caller.body()}
% endfor
</%def>
<%self:lister count="${3}">
hi
</%self:lister>
输出:
hi
hi
hi
这里的lister函数就带了一个叫count的参数,因此在调用的时候,使用count="${3}”传入该参数。这段代码需要注意的地方是这个函数需要的是一个整形,而参数的值需要用”"包含,因此我们使用了mako的表达式${3},这里表示传入了一个表达式,该表达式的值为3.
3. body参数
caller.body()输出的内容是调用者中间部分的模板内容,因此,我们也可以body传一个参数到这些模板内容里去。比如我们在body中传入一个参数caller.body(name=“cat"),那么在调用者模板内容中就可以用 hi ${name}来访问这个传入的参数。不过我们还需要在调用者那里也申明一下这个参数。例如:
<%def name="layoutdata(somedata)">
<table>
% for item in somedata:
<tr>
% for col in item:
<td>${caller.body(col=col)}</td>
% endfor
</tr>
% endfor
</table>
</%def>
<%self:layoutdata somedata="${[[1,2,3],[4,5,6],[7,8,9]]}" args="col">\
Body data: ${col}\
</%self:layoutdata>
输出:
<table>
<tr>
<td>Body data: 1</td>
<td>Body data: 2</td>
<td>Body data: 3</td>
</tr>
<tr>
<td>Body data: 4</td>
<td>Body data: 5</td>
<td>Body data: 6</td>
</tr>
<tr>
<td>Body data: 7</td>
<td>Body data: 8</td>
<td>Body data: 9</td>
</tr>
</table>
这段代码中:
a. ${caller.body(col=col)}中定义了一个col的参数,将每次循环的col值传入其中。
b. args="col”申明了col参数
c. Body data: ${col}\使用col参数输出,这里的\表示模板不换行
4. 自定义方法
body是mako中内置的方法,其实我们还可以在调用者模板内容处自定义需要的函数供外面的函数使用。
<%def name="layout()">
## a layout def
<div class="mainlayout">
<div class="header">
${caller.header()}
</div>
<div class="sidebar">
${caller.sidebar()}
</div>
<div class="content">
${caller.body()}
</div>
</div>
</%def>
## calls the layout def
<%self:layout>
<%def name="header()">
I am the header
</%def>
<%def name="sidebar()">
<ul>
<li>sidebar 1</li>
<li>sidebar 2</li>
</ul>
</%def>
this is the body
</%self:layout>
输出
<div class="mainlayout">
I am the header
</div>
<div class="sidebar">
<ul>
<li>sidebar 1</li>
<li>sidebar 2</li>
</ul>
</div>
<div class="content">
this is the body
</div>
</div>
在这个例子里,我们将调用者模板内容分成了三个部分,header、sidebar、以及剩下的body。因此我们就可以在layout函数中调用者三个函数。现在有模板特有的那种搭积木般的感觉了吧。只要把layout和调用者分别放入不同的文件中,layout就变成了一个页面框架的定义模板,不同的调用者可以使用这个公用的模板,而其中的内容各不相同。
mako中的函数不仅仅是函数,还有一种模板特有的调用方式。我们经常使用它来创建自定义标签:
1. 自定义标签
<%def name="buildtable()">
${caller.body()}
</%def>
<%self:buildtable>
I am the table body.
</%self:buildtable>
输出:
I am the table body.
self:buildtable 这种用法是mako0.2.3之后引入的一种用法,通过这种方式,可以创建任意的定制标签。在这段代码中,需要注意的是:
a. ${caller.body()}表示读取调用者的body部分内容,这里就是 I am the table body.
b. self为namespace,这里的self表示本模块。也可以是其他包含所需函数的模块名
2. 带参数的标签
函数可以带参数执行,做成自定义标签之后,也一样可以带入需要的参数,如:
<%def name="lister(count)">
% for x in range(count):
${caller.body()}
% endfor
</%def>
<%self:lister count="${3}">
hi
</%self:lister>
输出:
hi
hi
hi
这里的lister函数就带了一个叫count的参数,因此在调用的时候,使用count="${3}”传入该参数。这段代码需要注意的地方是这个函数需要的是一个整形,而参数的值需要用”"包含,因此我们使用了mako的表达式${3},这里表示传入了一个表达式,该表达式的值为3.
3. body参数
caller.body()输出的内容是调用者中间部分的模板内容,因此,我们也可以body传一个参数到这些模板内容里去。比如我们在body中传入一个参数caller.body(name=“cat"),那么在调用者模板内容中就可以用 hi ${name}来访问这个传入的参数。不过我们还需要在调用者那里也申明一下这个参数。例如:
<%def name="layoutdata(somedata)">
<table>
% for item in somedata:
<tr>
% for col in item:
<td>${caller.body(col=col)}</td>
% endfor
</tr>
% endfor
</table>
</%def>
<%self:layoutdata somedata="${[[1,2,3],[4,5,6],[7,8,9]]}" args="col">\
Body data: ${col}\
</%self:layoutdata>
输出:
<table>
<tr>
<td>Body data: 1</td>
<td>Body data: 2</td>
<td>Body data: 3</td>
</tr>
<tr>
<td>Body data: 4</td>
<td>Body data: 5</td>
<td>Body data: 6</td>
</tr>
<tr>
<td>Body data: 7</td>
<td>Body data: 8</td>
<td>Body data: 9</td>
</tr>
</table>
这段代码中:
a. ${caller.body(col=col)}中定义了一个col的参数,将每次循环的col值传入其中。
b. args="col”申明了col参数
c. Body data: ${col}\使用col参数输出,这里的\表示模板不换行
4. 自定义方法
body是mako中内置的方法,其实我们还可以在调用者模板内容处自定义需要的函数供外面的函数使用。
<%def name="layout()">
## a layout def
<div class="mainlayout">
<div class="header">
${caller.header()}
</div>
<div class="sidebar">
${caller.sidebar()}
</div>
<div class="content">
${caller.body()}
</div>
</div>
</%def>
## calls the layout def
<%self:layout>
<%def name="header()">
I am the header
</%def>
<%def name="sidebar()">
<ul>
<li>sidebar 1</li>
<li>sidebar 2</li>
</ul>
</%def>
this is the body
</%self:layout>
输出
<div class="mainlayout">
I am the header
</div>
<div class="sidebar">
<ul>
<li>sidebar 1</li>
<li>sidebar 2</li>
</ul>
</div>
<div class="content">
this is the body
</div>
</div>
在这个例子里,我们将调用者模板内容分成了三个部分,header、sidebar、以及剩下的body。因此我们就可以在layout函数中调用者三个函数。现在有模板特有的那种搭积木般的感觉了吧。只要把layout和调用者分别放入不同的文件中,layout就变成了一个页面框架的定义模板,不同的调用者可以使用这个公用的模板,而其中的内容各不相同。
mako二三事 -- 函数三
八 、mako中def的一些特定参数
前面我们就提到过,mako的函数是一种增强性的函数,除了可以往函数中传递参数之外,还可以为该函数定义一些额外的参数。例如,
1. cache
我们可以为一个mako函数单独定义一个cache,设定cache过期时间。
<%def name="somedef()"cached="True" cache_timeout="60">
<%
import time
%>
time = ${time.time()}
</%def>
${somedef()}
2. buffer
在写mako模板的时候,要牢牢记住的一点是:不论是否在函数内,模板内容都是直接写到输出的。也就是说,所有函数,不做特殊处理的话,它的返回值都是空字符串。这在平常可能不会注意到,但如果遇到类似如下的用法的时候就会有问题:
${" results " + somedef() + " more results"}
上面的${}中是一个表达式,这在mako中是合法的。如果这里的somedef函数输出somedef's results字符串,那我们会得到如下结果:
somedef's results results more results
这是因为,在函数somedef运行时,其运行结果就已经写到 输出中了。然后,somedef返回'',最后" results " + ''+ " more results"结果求值之后输出,这就是上面这个结果。
有的时候,我们希望将somedef这个函数的运行结果先暂存起来,经过一定处理之后,再写入到输出中。因此,mako引入了buffered这个参数来解决这个问题。(注意,这里的buffer跟cache是完全不同的概念。)
<%def name="somedef()" buffered="True">
somedef's results
</%def>
${" results " + somedef() + " more results"}
输出:
results somedef's results more results
我们来看一下这种方式生成的py文件:
def render_somedef(context):
__M_caller = context.caller_stack._push_frame()
try:
context._push_buffer()
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u"\n somedef's results\n")
finally:
__M_buf = context._pop_buffer()
context.caller_stack._pop_frame()
return __M_buf.getvalue()
可以看到,该函数创建了一个暂存区,然后将其中的数据作为了return的返回值。
如果只是希望临时性的获取一下mako函数运行结果进行一些测试,而不是直接buffer起来,mako也提供了一个叫capture的方法。通过capture,可以截获一个普通的mako的运行结果,转成返回值。例如
<%def name="somedef(age=8, weight=100)">
somedef's results ${age} ${weight}
</%def>
${" results " + capture(somedef, 10, weight=120) + " more results"}
输出:
results somedef's results 10 120 more results
3. filter
mako本身是一个模板,因此也就带了一系列的filter,通过这些filter的作用,可以很灵活的将数据转换成特定的格式。在mako定义函数时,也引入了filter概念。注意,因为需要filter,也就必须要截获函数的运行结果,因此,filter默认也带有buffer功能。例如
<%def name="foo()" filter="h, trim">
this is bold
</%def>
${foo()}
输出为:
<b>this is bold</b>
这里需要注意的是,filter是def的参数,不是foo的参数。这个filter仅仅作用于该函数的生成结果。
4. Decorating
除了filter之外,mako还提供了Decorating的功能,不过因为模板的特殊性,mako函数的Decorating跟python中的还是有很多区别的。例如
<%!
def bar(fn):
def decorate(context, *args, **kw):
context.write("BAR")
fn(*args, **kw)
context.write("BAR")
return 'mytest'
return decorate
%>
<%def name="foo()" decorator="bar">
this is foo
</%def>
${"abc"+foo()}
输出:
BAR
this is foo
BARabcmytest
注意:
a. mako里面所有的函数第一个参数都是context,所以 decorate函数的第一个参数必须是context。
b. 如果希望在decorate输出内容,可以使用context.write函数,这也是mako推荐的方式。
c. 这里的decorate应该返回'',像上示例中返回其他内容的话会加到输出结果中去,但要注意加入的位置。
d. 如果用template.get_def('somedef').render()之类的方法来渲染模板,则只会使用context.write的结果,抛弃return返回值。
>>> print template.get_def("foo").render()
BAR
this is foo
BAR
另外,capture函数也可以用于构建decorate,例如:
<%!
def bar(fn):
def decorate(context, *args, **kw):
return "BAR" + runtime.capture(context, fn, *args, **kw) + "BAR"
return decorate
%>
<%def name="foo()" decorator="bar">
this is foo
</%def>
${foo()}
前面我们就提到过,mako的函数是一种增强性的函数,除了可以往函数中传递参数之外,还可以为该函数定义一些额外的参数。例如,
1. cache
我们可以为一个mako函数单独定义一个cache,设定cache过期时间。
<%def name="somedef()"cached="True" cache_timeout="60">
<%
import time
%>
time = ${time.time()}
</%def>
${somedef()}
2. buffer
在写mako模板的时候,要牢牢记住的一点是:不论是否在函数内,模板内容都是直接写到输出的。也就是说,所有函数,不做特殊处理的话,它的返回值都是空字符串。这在平常可能不会注意到,但如果遇到类似如下的用法的时候就会有问题:
${" results " + somedef() + " more results"}
上面的${}中是一个表达式,这在mako中是合法的。如果这里的somedef函数输出somedef's results字符串,那我们会得到如下结果:
somedef's results results more results
这是因为,在函数somedef运行时,其运行结果就已经写到 输出中了。然后,somedef返回'',最后" results " + ''+ " more results"结果求值之后输出,这就是上面这个结果。
有的时候,我们希望将somedef这个函数的运行结果先暂存起来,经过一定处理之后,再写入到输出中。因此,mako引入了buffered这个参数来解决这个问题。(注意,这里的buffer跟cache是完全不同的概念。)
<%def name="somedef()" buffered="True">
somedef's results
</%def>
${" results " + somedef() + " more results"}
输出:
results somedef's results more results
我们来看一下这种方式生成的py文件:
def render_somedef(context):
__M_caller = context.caller_stack._push_frame()
try:
context._push_buffer()
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u"\n somedef's results\n")
finally:
__M_buf = context._pop_buffer()
context.caller_stack._pop_frame()
return __M_buf.getvalue()
可以看到,该函数创建了一个暂存区,然后将其中的数据作为了return的返回值。
如果只是希望临时性的获取一下mako函数运行结果进行一些测试,而不是直接buffer起来,mako也提供了一个叫capture的方法。通过capture,可以截获一个普通的mako的运行结果,转成返回值。例如
<%def name="somedef(age=8, weight=100)">
somedef's results ${age} ${weight}
</%def>
${" results " + capture(somedef, 10, weight=120) + " more results"}
输出:
results somedef's results 10 120 more results
3. filter
mako本身是一个模板,因此也就带了一系列的filter,通过这些filter的作用,可以很灵活的将数据转换成特定的格式。在mako定义函数时,也引入了filter概念。注意,因为需要filter,也就必须要截获函数的运行结果,因此,filter默认也带有buffer功能。例如
<%def name="foo()" filter="h, trim">
this is bold
</%def>
${foo()}
输出为:
<b>this is bold</b>
这里需要注意的是,filter是def的参数,不是foo的参数。这个filter仅仅作用于该函数的生成结果。
4. Decorating
除了filter之外,mako还提供了Decorating的功能,不过因为模板的特殊性,mako函数的Decorating跟python中的还是有很多区别的。例如
<%!
def bar(fn):
def decorate(context, *args, **kw):
context.write("BAR")
fn(*args, **kw)
context.write("BAR")
return 'mytest'
return decorate
%>
<%def name="foo()" decorator="bar">
this is foo
</%def>
${"abc"+foo()}
输出:
BAR
this is foo
BARabcmytest
注意:
a. mako里面所有的函数第一个参数都是context,所以 decorate函数的第一个参数必须是context。
b. 如果希望在decorate输出内容,可以使用context.write函数,这也是mako推荐的方式。
c. 这里的decorate应该返回'',像上示例中返回其他内容的话会加到输出结果中去,但要注意加入的位置。
d. 如果用template.get_def('somedef').render()之类的方法来渲染模板,则只会使用context.write的结果,抛弃return返回值。
>>> print template.get_def("foo").render()
BAR
this is foo
BAR
另外,capture函数也可以用于构建decorate,例如:
<%!
def bar(fn):
def decorate(context, *args, **kw):
return "BAR" + runtime.capture(context, fn, *args, **kw) + "BAR"
return decorate
%>
<%def name="foo()" decorator="bar">
this is foo
</%def>
${foo()}
Sunday, December 1, 2013
mako二三事 -- 函数二
五、调用其他模板中的函数
mako也能支持调用其他模板中的函数,因此,多个函数可以集合成一个mako函数库。不过mako仅支持顶层函数的跨模块调用,而且需要通过namespace标签来引入,例如,在mystuff.html中定义了一个函数account:
<%namespace name="mystuff" file="mystuff.html"/>
如上定义之后,就可以使用
${mystuff.account()}
来调用该函数了。这种方式看起来跟python中的import XXX as XXX 是一致的。不过mako的实现还是不一样的,我们来看一下这个模板编译之后的代码:
def _mako_get_namespace(context, name):
try:
return context.namespaces[(__name__, name)] # namespace缓存机制,如果已经存在,不需要再载入这个namespace
except KeyError:
_mako_generate_namespaces(context)
return context.namespaces[(__name__, name)]
def _mako_generate_namespaces(context):
# SOURCE LINE 3 # namespace语句对应的生成语句,每个namespace语句分别生成。
ns = runtime.TemplateNamespace(u'mystuff', context._clean_inheritance_tokens(), templateuri=u'mystuff.html', callables=None, calling_uri=_template_uri)
context.namespaces[(__name__, u'mystuff')] = ns # 将namespace缓存到context中
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
mystuff = _mako_get_namespace(context, 'mystuff’) # 从namespace中取得该函数,即可使用
__M_writer = context.writer()
# SOURCE LINE 2
__M_writer(u'\n')
# SOURCE LINE 3
__M_writer(u' \n')
# SOURCE LINE 4
__M_writer(unicode(mystuff.account())) # 函数调用
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
python支持from XXXX import XX, XXX这种import方式,mako也支持类似方式,如:
<%namespace file="mystuff.html" import="foo, bar"/>
<%namespace file="mystuff.html" import=“*"/>
六、在python中调用模板函数
mako支持通过python程序来调用模板函数,这种方式在某些特定场合可以使用。如:
from mako.template import Template
template = Template("""
<%def name="hi(name)">
hi ${name}!
</%def>
<%def name="bye(name)">
bye ${name}!
</%def>
""")
print template.get_def("hi").render(name="ed")
print template.get_def("bye").render(name="ed")
七、嵌套函数
mako也可以像python一样支持嵌套函数,而且其作用也跟python中的一模一样,编译成py文件后也是嵌套函数,因此其中变量的作用域也跟python中的类似。注意,内嵌的函数不能被其他模块引用。
<%
x = 12
%>
<%def name="outer()">
<%
y = 15
%>
<%def name="inner()">
inner, x is ${x}, y is ${y}
</%def>
${inner()}
outer, x is ${x}, y is ${y}
</%def>
${outer()}
输出:
inner, x is 12, y is 15
outer, x is 12, y is 15
在上面代码中,还需要注意的一点是,这里的x并不会生成一个全局的变量,在模板里写的所有内容都会被包括在一个叫render_body的函数里面,如果需要在多个模板之间传递、共享一些数据需要使用特殊的技巧,这个在Context里面会讲到。
Saturday, November 30, 2013
mako二三事 -- 函数一
跟python一样,在mako中,也可以用def来定义一个函数。不过在功能上,mako中的def比python稍微强大一点。
一、函数定义
在mako中,使用<%def>来定义一个函数。注意,这里函数名需要表示成name参数的值。
<%def name="hello()">
hello world
</%def>
定义完函数之后,就可以在模板中直接调用该函数:
call def: ${hello()}
通过这种方式定义的函数是mako的顶层函数(top level def),它可以在该模板任意地方使用,也可以通过namespace在其他模板中使用。
二、函数定义的背后
在mako的运行过程中,所有模板都会生成对应的py文件,浏览这些文件可以很好的理解mako背后的运行机制。在mako中,每一个模板对应的py文件中,都会有一个render_body函数,它是模板的主路口,模板render就是调用这个函数完成整个模板的渲染的。如果在模板中,定义了顶层函数,那么还会生成一个类似render_hello这样的函数,这里hello是具体的函数名。我们上面的例子在编译成py文件之后,就会得到如下代码:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
def hello(): # hello函数,这个函数只完成对render_hello的一个调用,并将上下文作为参数传递下去
return render_hello(context.locals_(__M_locals))
__M_writer = context.writer()
# SOURCE LINE 3
__M_writer(u'\n\ncall def: ')
# SOURCE LINE 5
__M_writer(unicode(hello())) # 调用hello函数
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
def render_hello(context):
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'\n hello world\n’) # 显示hello world
return ''
finally:
context.caller_stack._pop_frame()
三、参数的传递
其实,在mako中,说起函数参数可分为def定义的参数和传入到函数的数据参数两种,就像上面提到的函数名name=“hello()”,其实是def的参数,def还有几个类似的参数,我们后面会深入讨论;而如果我们需要往这个函数中传入一些参数量,则也需要写在name这个变量名中,如:
${account(accountname='john')}
<%def name="account(accountname, type='regular')">
account name: ${accountname}, type: ${type}
</%def>
上述代码定义了一个函数account,该函数带两个参数accountname、type,其中type带有默认值。这些参数的使用方式跟python中函数的定义都是一致的。
使用了函数参数之后,编译之后生成的代码片段如下:
def account(accountname,type='regular'):
return render_account(context.locals_(__M_locals),accountname,type)
因此,可以看到,mako仅仅是将这些参数原原本本的转到了python里面而已。因此,所有跟python函数参数相关的规则都适用于mako函数参数。
四、变量的传递
在mako中,还会涉及到render传入的参数变量、模板中定义的变量等。这些变量的处理方式跟函数参数是不一样的。下面我们来看个例子:
Hello there ${username}, how are ya. Lets see what your account says:
<%
module_local_var = 'module_local_var'
%>
${account(accountname='john')}
<%def name="account(accountname)">
<%
account_local_var = 'account_local_var'
%>
Account name for ${username}: ${accountname}
module_local_var = ${module_local_var}
account_local_var = ${account_local_var}
</%def>
这段程序有四种变量:
* username,从render(或framework)传入,放入了context中,因此,可以直接在模板中引用。
* module_local_var ,是在模板中定义的变量
* account_local_var ,是在一个模板函数中定义的变量
*accountname,是函数的参数变量,在函数调用的时候指定。
下面我们来看一下编译生成的py文件:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
username = context.get('username', UNDEFINED) # username是从context中取得的,如果没有定义,则默认使用UNDEFINED
def account(accountname):
return render_account(context.locals_(__M_locals),accountname) # accountname 作为函数参数传入
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'Hello there ')
__M_writer(unicode(username))
__M_writer(u', how are ya. Lets see what your account says:\n')
# SOURCE LINE 2
module_local_var = ‘module_local_var’ # 在模板中定义的变量,现在是render_body函数的变量
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['module_local_var'] if __M_key in __M_locals_builtin_stored])) # 将module_local_var 放入__M_locals中,这样render_account函数就能从context中取得该值
# SOURCE LINE 4
__M_writer(u'\n')
# SOURCE LINE 5
__M_writer(unicode(account(accountname='john'))) # 函数调用,传入参数值
__M_writer(u'\n\n')
# SOURCE LINE 14
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
def render_account(context,accountname):
__M_caller = context.caller_stack._push_frame()
try:
username = context.get('username', UNDEFINED) # 在函数中再次取得username,两个函数之间通过context传递信息
module_local_var = context.get('module_local_var', UNDEFINED) # 从context中取得module_local_var,这也是模板级别的变量传递的途径
__M_writer = context.writer()
# SOURCE LINE 7
__M_writer(u'\n')
# SOURCE LINE 8
account_local_var = ‘account_local_var’ # 函数内部定义的变量,不用变化
# SOURCE LINE 10
__M_writer(u'\n Account name for ')
# SOURCE LINE 11
__M_writer(unicode(username))
__M_writer(u': ')
__M_writer(unicode(accountname))
__M_writer(u'
\n module_local_var = ')
# SOURCE LINE 12
__M_writer(unicode(module_local_var))
__M_writer(u'
\n account_local_var = ')
# SOURCE LINE 13
__M_writer(unicode(account_local_var))
__M_writer(u'
\n')
return ''
finally:
context.caller_stack._pop_frame()
由上面的代码,我们可以知道,render传入的参数、模板中定义的变量都是需要经过context传递的。
再来看一个错误使用参数的例子
<%
module_local_var = 'module_local_var'
%>
${account()}
<%def name="account()">
module_local_var = ${module_local_var}
<%
module_local_var = 'new_module_local_var'
%>
</%def>
这里的区别是,我们在函数中重新赋值了module_local_var,并且在赋值之前使用了该变量,我们知道这在python中是要出错的,因此在mako中也必须要出错,要不就混淆了。我们看看生成的代码:
def render_account(context):
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 6
__M_writer(u'\n module_local_var = ')
# SOURCE LINE 7
__M_writer(unicode(module_local_var))
__M_writer(u'
\n')
# SOURCE LINE 8
module_local_var = 'new_module_local_var'
# SOURCE LINE 10
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
从这段代码可以看出,只要在函数中重新赋值了module_local_var 这个变量,那么这个变量就是一个函数内的局部变量,就没有再从context取变量这个步骤了,也因此,这段代码显然就报错了。
一、函数定义
在mako中,使用<%def>来定义一个函数。注意,这里函数名需要表示成name参数的值。
<%def name="hello()">
hello world
</%def>
定义完函数之后,就可以在模板中直接调用该函数:
call def: ${hello()}
通过这种方式定义的函数是mako的顶层函数(top level def),它可以在该模板任意地方使用,也可以通过namespace在其他模板中使用。
二、函数定义的背后
在mako的运行过程中,所有模板都会生成对应的py文件,浏览这些文件可以很好的理解mako背后的运行机制。在mako中,每一个模板对应的py文件中,都会有一个render_body函数,它是模板的主路口,模板render就是调用这个函数完成整个模板的渲染的。如果在模板中,定义了顶层函数,那么还会生成一个类似render_hello这样的函数,这里hello是具体的函数名。我们上面的例子在编译成py文件之后,就会得到如下代码:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
def hello(): # hello函数,这个函数只完成对render_hello的一个调用,并将上下文作为参数传递下去
return render_hello(context.locals_(__M_locals))
__M_writer = context.writer()
# SOURCE LINE 3
__M_writer(u'\n\ncall def: ')
# SOURCE LINE 5
__M_writer(unicode(hello())) # 调用hello函数
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
def render_hello(context):
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'\n hello world\n’) # 显示hello world
return ''
finally:
context.caller_stack._pop_frame()
三、参数的传递
其实,在mako中,说起函数参数可分为def定义的参数和传入到函数的数据参数两种,就像上面提到的函数名name=“hello()”,其实是def的参数,def还有几个类似的参数,我们后面会深入讨论;而如果我们需要往这个函数中传入一些参数量,则也需要写在name这个变量名中,如:
${account(accountname='john')}
<%def name="account(accountname, type='regular')">
account name: ${accountname}, type: ${type}
</%def>
上述代码定义了一个函数account,该函数带两个参数accountname、type,其中type带有默认值。这些参数的使用方式跟python中函数的定义都是一致的。
使用了函数参数之后,编译之后生成的代码片段如下:
def account(accountname,type='regular'):
return render_account(context.locals_(__M_locals),accountname,type)
因此,可以看到,mako仅仅是将这些参数原原本本的转到了python里面而已。因此,所有跟python函数参数相关的规则都适用于mako函数参数。
四、变量的传递
在mako中,还会涉及到render传入的参数变量、模板中定义的变量等。这些变量的处理方式跟函数参数是不一样的。下面我们来看个例子:
Hello there ${username}, how are ya. Lets see what your account says:
<%
module_local_var = 'module_local_var'
%>
${account(accountname='john')}
<%def name="account(accountname)">
<%
account_local_var = 'account_local_var'
%>
Account name for ${username}: ${accountname}
module_local_var = ${module_local_var}
account_local_var = ${account_local_var}
</%def>
这段程序有四种变量:
* username,从render(或framework)传入,放入了context中,因此,可以直接在模板中引用。
* module_local_var ,是在模板中定义的变量
* account_local_var ,是在一个模板函数中定义的变量
*accountname,是函数的参数变量,在函数调用的时候指定。
下面我们来看一下编译生成的py文件:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
username = context.get('username', UNDEFINED) # username是从context中取得的,如果没有定义,则默认使用UNDEFINED
def account(accountname):
return render_account(context.locals_(__M_locals),accountname) # accountname 作为函数参数传入
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'Hello there ')
__M_writer(unicode(username))
__M_writer(u', how are ya. Lets see what your account says:\n')
# SOURCE LINE 2
module_local_var = ‘module_local_var’ # 在模板中定义的变量,现在是render_body函数的变量
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['module_local_var'] if __M_key in __M_locals_builtin_stored])) # 将module_local_var 放入__M_locals中,这样render_account函数就能从context中取得该值
# SOURCE LINE 4
__M_writer(u'\n')
# SOURCE LINE 5
__M_writer(unicode(account(accountname='john'))) # 函数调用,传入参数值
__M_writer(u'\n\n')
# SOURCE LINE 14
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
def render_account(context,accountname):
__M_caller = context.caller_stack._push_frame()
try:
username = context.get('username', UNDEFINED) # 在函数中再次取得username,两个函数之间通过context传递信息
module_local_var = context.get('module_local_var', UNDEFINED) # 从context中取得module_local_var,这也是模板级别的变量传递的途径
__M_writer = context.writer()
# SOURCE LINE 7
__M_writer(u'\n')
# SOURCE LINE 8
account_local_var = ‘account_local_var’ # 函数内部定义的变量,不用变化
# SOURCE LINE 10
__M_writer(u'\n Account name for ')
# SOURCE LINE 11
__M_writer(unicode(username))
__M_writer(u': ')
__M_writer(unicode(accountname))
__M_writer(u'
\n module_local_var = ')
# SOURCE LINE 12
__M_writer(unicode(module_local_var))
__M_writer(u'
\n account_local_var = ')
# SOURCE LINE 13
__M_writer(unicode(account_local_var))
__M_writer(u'
\n')
return ''
finally:
context.caller_stack._pop_frame()
由上面的代码,我们可以知道,render传入的参数、模板中定义的变量都是需要经过context传递的。
再来看一个错误使用参数的例子
<%
module_local_var = 'module_local_var'
%>
${account()}
<%def name="account()">
module_local_var = ${module_local_var}
<%
module_local_var = 'new_module_local_var'
%>
</%def>
这里的区别是,我们在函数中重新赋值了module_local_var,并且在赋值之前使用了该变量,我们知道这在python中是要出错的,因此在mako中也必须要出错,要不就混淆了。我们看看生成的代码:
def render_account(context):
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 6
__M_writer(u'\n module_local_var = ')
# SOURCE LINE 7
__M_writer(unicode(module_local_var))
__M_writer(u'
\n')
# SOURCE LINE 8
module_local_var = 'new_module_local_var'
# SOURCE LINE 10
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
从这段代码可以看出,只要在函数中重新赋值了module_local_var 这个变量,那么这个变量就是一个函数内的局部变量,就没有再从context取变量这个步骤了,也因此,这段代码显然就报错了。
Thursday, November 28, 2013
mako二三事 -- 与Babel的结合
为了支持国际化,在python程序中,经常需要使用Babel将语言相关的字符串抽取出来,形成pot文件。而在mako模板文件中,有大量的文本性内容需要国际化处理,因此,mako必须要支持Babel的抽取功能。
一、mako字符串的简单抽取
在mako中,使用了一个名为mako.ext.babelplugin的Babel抽取插件,正确安装Babel、mako后,就可以使用pybabel命令进行字符串抽取。
首先,我们创建一个a.html文件,内容为:
Name:
${_('Francois Pinard')}
再创建一个babel.cfg文件,作为Babel 的配置文件:
[mako: **.html]
input_encoding = utf-8
然后运行命令
pybabel extract -F babel.cfg .
即可在console输出:
extracting messages from a.html (input_encoding="utf-8")
# Translations template for PROJECT.
# Copyright (C) 2013 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR, 2013.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-11-26 16:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
#: a.html:3
msgid "Francois Pinard"
msgstr ""
这个就是我们通常使用的pot文件,有了pot文件,我们就可以很方便的按照国际化命令生成po文件,mo文件。
在a.html中,我们可以看到的一个明显的变化是:Francois Pinard这段文本被改写成了${_('Francois Pinard’)},mako的Babel插件能够识别这种格式并抽取到pot文件中,而普通的文本name:则不会。
Babel进行抽取的时候,需要指定一个配置文件,这里我们做了一个简单的配置文件babel.cfg。在该文件中,**.html表示本目录以及子目录所有html文件,前面的mako表示这些文件均有mako的Babel插件来解析。input_encoding为模板的编码格式,这个跟模板载入时用的encoding是一致的。
二、对抽取字符串做注释
有时候,为了能够清楚分辨抽取字符串,可以对这些字符串做一个注释。
首先,我们修改a.html为:
Name:
## TRANSLATORS: This is a proper name. See the gettext
## manual, section Names.
${_('Francois Pinard')}
这里要注意的是,这个注释必须直接放在抽取字符串之前,中间不能有其他多余字符串。
然后,运行
pybabel extract -F babel.cfg -c "TRANSLATORS:” .
可以得到:
#. TRANSLATORS: This is a proper name. See the gettext
#. manual, section Names.
#: a.html:5
msgid "Francois Pinard"
msgstr ""
从上面结果可以看到,在pot文件中,将html文件里面对这个字符串的注释也抽取到了该文件中。
三、pyramid项目的支持
通常的,我们不会简单的对一个文件进行国际化,而是对一个使用了特定framework的项目进行国际化。比如使用了pyramid框架,通过pcreate生成的项目中,会有setup.py和setup.cfg两个文件,为了支持Babel抽取,需要在setup.py中加入mako配置(setup中加两个参数):
package_data={'mypyramid': ['locale/*/LC_MESSAGES/*.mo']},
message_extractors = { 'mypyramid': [
('**.py', 'lingua_python', None ),
('templates/**.pt', 'lingua_xml', None ),
('templates/**.html', 'mako', None),
('static/**', 'ignore', None),
]},
然后在mypyramid目录下建立locale文件夹即可。这里的'templates/**.html', 'mako', None含义跟上面的配置文件含义一致。
另外,在setup.cfg中,有国际化字符串抽取、编译、更新的系列配置,如果有需要,可以调整这些参数。
[compile_catalog]
directory = mypyramid/locale
domain = MyPyramid
statistics = true
[extract_messages]
add_comments = TRANSLATORS:
output_file = mypyramid/locale/MyPyramid.pot
width = 80
[init_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
[update_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
previous = true
在pyramid项目中,为了支持国际化,还需要从python程序中将_这个函数压入到context中,如下:
tsf = TranslationStringFactory('MyPyramid’)
@subscriber(BeforeRender)
def add_renderer_globals(event):
def auto_translate(string, domain=None, mapping=None):
return localizer.translate(tsf(string), domain=domain, mapping=mapping)
request = event.get('request')
if request:
event['_'] = auto_translate
一、mako字符串的简单抽取
在mako中,使用了一个名为mako.ext.babelplugin的Babel抽取插件,正确安装Babel、mako后,就可以使用pybabel命令进行字符串抽取。
首先,我们创建一个a.html文件,内容为:
Name:
${_('Francois Pinard')}
再创建一个babel.cfg文件,作为Babel 的配置文件:
[mako: **.html]
input_encoding = utf-8
然后运行命令
pybabel extract -F babel.cfg .
即可在console输出:
extracting messages from a.html (input_encoding="utf-8")
# Translations template for PROJECT.
# Copyright (C) 2013 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-11-26 16:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
#: a.html:3
msgid "Francois Pinard"
msgstr ""
这个就是我们通常使用的pot文件,有了pot文件,我们就可以很方便的按照国际化命令生成po文件,mo文件。
在a.html中,我们可以看到的一个明显的变化是:Francois Pinard这段文本被改写成了${_('Francois Pinard’)},mako的Babel插件能够识别这种格式并抽取到pot文件中,而普通的文本name:则不会。
Babel进行抽取的时候,需要指定一个配置文件,这里我们做了一个简单的配置文件babel.cfg。在该文件中,**.html表示本目录以及子目录所有html文件,前面的mako表示这些文件均有mako的Babel插件来解析。input_encoding为模板的编码格式,这个跟模板载入时用的encoding是一致的。
二、对抽取字符串做注释
有时候,为了能够清楚分辨抽取字符串,可以对这些字符串做一个注释。
首先,我们修改a.html为:
Name:
## TRANSLATORS: This is a proper name. See the gettext
## manual, section Names.
${_('Francois Pinard')}
这里要注意的是,这个注释必须直接放在抽取字符串之前,中间不能有其他多余字符串。
然后,运行
pybabel extract -F babel.cfg -c "TRANSLATORS:” .
可以得到:
#. TRANSLATORS: This is a proper name. See the gettext
#. manual, section Names.
#: a.html:5
msgid "Francois Pinard"
msgstr ""
从上面结果可以看到,在pot文件中,将html文件里面对这个字符串的注释也抽取到了该文件中。
三、pyramid项目的支持
通常的,我们不会简单的对一个文件进行国际化,而是对一个使用了特定framework的项目进行国际化。比如使用了pyramid框架,通过pcreate生成的项目中,会有setup.py和setup.cfg两个文件,为了支持Babel抽取,需要在setup.py中加入mako配置(setup中加两个参数):
package_data={'mypyramid': ['locale/*/LC_MESSAGES/*.mo']},
message_extractors = { 'mypyramid': [
('**.py', 'lingua_python', None ),
('templates/**.pt', 'lingua_xml', None ),
('templates/**.html', 'mako', None),
('static/**', 'ignore', None),
]},
然后在mypyramid目录下建立locale文件夹即可。这里的'templates/**.html', 'mako', None含义跟上面的配置文件含义一致。
另外,在setup.cfg中,有国际化字符串抽取、编译、更新的系列配置,如果有需要,可以调整这些参数。
[compile_catalog]
directory = mypyramid/locale
domain = MyPyramid
statistics = true
[extract_messages]
add_comments = TRANSLATORS:
output_file = mypyramid/locale/MyPyramid.pot
width = 80
[init_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
[update_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
previous = true
在pyramid项目中,为了支持国际化,还需要从python程序中将_这个函数压入到context中,如下:
tsf = TranslationStringFactory('MyPyramid’)
@subscriber(BeforeRender)
def add_renderer_globals(event):
def auto_translate(string, domain=None, mapping=None):
return localizer.translate(tsf(string), domain=domain, mapping=mapping)
request = event.get('request')
if request:
event['_'] = auto_translate
Tuesday, November 26, 2013
mako二三事 -- run_wsgi
mako源码的example目录里有一个run_wsgi.py文件,是一个简单的的wsgi程序,它的主要功能就是以templates或htdocs目录作为根目录,启动一个简单的wsgi服务,该服务读入GET或POST中的参数,以及PATH_INFO作为模板文件名,然后将GET/POST的参数均放入模板context中进行模板渲染,因此,该功能可以在没有程序支持的情况下单独调试模板。
不过该程序有两个小问题:
1. 32行的 return [template.render(**d)] 最好改成return [template.render_unicode(**d).encode(‘utf-8’)],然后以utf-8格式输出。有些场景下,不转码会报错。
2. 对于非html文件,它的是读取run_wsgi.py同级目录的,这在项目中一般不合适,因此
u = re.sub(r'^\/+', '', uri)
filename = os.path.join(root, u)
这里需要看具体项目加上静态文件的目录。
在运行python run_wsgi.py之后,该程序会监听8000端口。因此,可以在浏览器中使用http://http://127.0.0.1:8000/d.html 查看d.html模板。
如果模板中有参数,就可以在链接中加入需要的参数值。如下为d.html模板:
test ${a}
则查看http://127.0.0.1:8000/d.html?a=mako,则会显示test mako
如果模板中有数组参数,如:
test ${a}
% for i, value in enumerate(names):
${i+1}. ${value}
% endfor
则可以使用链接http://127.0.0.1:8000/d.html?a=mako&names=tom&names=cat 进行查看,显示如下内容:
test mako
1. tom
2. cat
由此可以看出,通过url中参数的不同组合,可以在没有业务逻辑程序的支持下直接调试模板,方便了模板的编写和分工。
不过该程序有两个小问题:
1. 32行的 return [template.render(**d)] 最好改成return [template.render_unicode(**d).encode(‘utf-8’)],然后以utf-8格式输出。有些场景下,不转码会报错。
2. 对于非html文件,它的是读取run_wsgi.py同级目录的,这在项目中一般不合适,因此
u = re.sub(r'^\/+', '', uri)
filename = os.path.join(root, u)
这里需要看具体项目加上静态文件的目录。
在运行python run_wsgi.py之后,该程序会监听8000端口。因此,可以在浏览器中使用http://http://127.0.0.1:8000/d.html 查看d.html模板。
如果模板中有参数,就可以在链接中加入需要的参数值。如下为d.html模板:
test ${a}
则查看http://127.0.0.1:8000/d.html?a=mako,则会显示test mako
如果模板中有数组参数,如:
test ${a}
% for i, value in enumerate(names):
${i+1}. ${value}
% endfor
则可以使用链接http://127.0.0.1:8000/d.html?a=mako&names=tom&names=cat 进行查看,显示如下内容:
test mako
1. tom
2. cat
由此可以看出,通过url中参数的不同组合,可以在没有业务逻辑程序的支持下直接调试模板,方便了模板的编写和分工。
Sunday, November 24, 2013
mako二三事 -- Exceptions
mako模板运行前,会被编译成py文件,因此一旦有模板中有问题,python抛出的出错信息(如文件名、行号、出错代码等)都是针对该py文件的,因此mako需要将这些Exceptions信息转换成模板对应的信息。
如python抛出的exception内容为:
File "/tmp/basic.html.py", line 49, in render_body
__M_writer(unicode(no1_value))
mako转换过之后的exception内容为:
File "/dir1/basic.html", line 12, in render_body
None value=${no1_value}
mako.exceptions是mako用于处理异常的主要代码,主要函数包括:
一、text_error_template
text_error_template用于输出文本格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.text_error_template().render()
二、html_error_template
html_error_template用于输出HTML格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.html_error_template().render()
html_error_template函数带 full、css两个参数,默认都是True。
full参数表示是否生成完整的HTML页面结构,即带等信息。
css参数表示是否生成自带的CSS信息。
通过指定这两个参数,可以将异常页面更好的融合到应用系统中。
Template类、TemplateLookup类都有一个format_exceptions,置为True,则mako默认将所有exception都转出html格式,即便是用text_error_template输出。
三、RichTraceback
mako生成上述出错信息的底层信息类就是RichTraceback,可以通过RichTraceback生成用户自定义的出错信息格式。from mako.exceptions import RichTraceback
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
template = mylookup.get_template("basic.html")
print template.render()
except:
traceback = RichTraceback()
for (filename, lineno, function, line) in traceback.traceback:
print "File %s, line %s, in %s" % (filename, lineno, function)
print line, "\n"
print "%s: %s" % (str(traceback.error.__class__.__name__), traceback.error)
traceback.traceback为调用栈列表。
traceback.errorname为exception名称
traceback.lineno为模板行数
traceback.message为出错信息
如python抛出的exception内容为:
File "/tmp/basic.html.py", line 49, in render_body
__M_writer(unicode(no1_value))
mako转换过之后的exception内容为:
File "/dir1/basic.html", line 12, in render_body
None value=${no1_value}
mako.exceptions是mako用于处理异常的主要代码,主要函数包括:
一、text_error_template
text_error_template用于输出文本格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.text_error_template().render()
二、html_error_template
html_error_template用于输出HTML格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.html_error_template().render()
html_error_template函数带 full、css两个参数,默认都是True。
full参数表示是否生成完整的HTML页面结构,即带等信息。
css参数表示是否生成自带的CSS信息。
通过指定这两个参数,可以将异常页面更好的融合到应用系统中。
Template类、TemplateLookup类都有一个format_exceptions,置为True,则mako默认将所有exception都转出html格式,即便是用text_error_template输出。
三、RichTraceback
mako生成上述出错信息的底层信息类就是RichTraceback,可以通过RichTraceback生成用户自定义的出错信息格式。from mako.exceptions import RichTraceback
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
template = mylookup.get_template("basic.html")
print template.render()
except:
traceback = RichTraceback()
for (filename, lineno, function, line) in traceback.traceback:
print "File %s, line %s, in %s" % (filename, lineno, function)
print line, "\n"
print "%s: %s" % (str(traceback.error.__class__.__name__), traceback.error)
traceback.traceback为调用栈列表。
traceback.errorname为exception名称
traceback.lineno为模板行数
traceback.message为出错信息
mako二三事 -- 模板载入与encoding
mako是python中比较流行的一个模板引擎,网络上也有很多相关的介绍,因此这个系列文章主要关注mako中一些平常不太注意的内容。
一、字符串直接渲染
mako可以直接对一段字符串进行渲染,不过这没有多少实际意义,直接用用%或字符串拼接即可,基本用于测试演示用途。
from mako.template import Template
mytemplate = Template("hello, ${name}!")
print mytemplate.render(name="jack")
如果字符串中包含了中文等内容,需要使用unicode类型:
mytemplate = Template(u"你好, ${name}!")
print mytemplate.render(name="jack")
如果不希望使用unicode类型,则需要指定特定的input_encoding
mytemplate = Template("你好, ${name}!", input_encoding="utf-8")
print mytemplate.render(name="jack")
二、单模板渲染
mako也可以载入单个模板文件进行渲染,只要指定文件的绝对路径即可
from mako.template import Template
mytemplate = Template(filename='/dir1/a.html')
print mytemplate.render()
如果a.html有特定的编码格式,也需要指定input_encoding
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8")
print mytemplate.render()
Template中还可以指定模板编译之后的中间文件存储路径,可以方便模板语法问题的调试。
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8", module_directory='/tmp')
print mytemplate.render()
注意,mako会在/tmp目录下生成/dir1/a.html.py文件,也就是说会将filename中包含的文件路径在module_directory中生成一遍。
但是如果这个模板中包含了include之类的mako语法,这种载入方式就会因找不到文件而报错。
三、模板lookup
在mako最常用的还是TemplateLookup,为mako指定一个模板文件存放路径,而将模板查找工作交给mako处理。
from mako.template import Template
from mako.lookup import TemplateLookup
mylookup = TemplateLookup(directories=['/dir1','/dir2' ])
mytemplate = mylookup.get_template("a.html")
TemplateLookup中,可以指定多个路径,mako按顺序逐个查找,直到找到模板文件。使用了TemplateLookup之后,建议使用get_template函数得到模板。Template类中尽管可以指定lookup参数,但其他参数无法从lookup中取得预先设定的信息,会引起很多错误。
如有一下目录结构:
/dir1
a.html(模板中include了c.html)
c.html(模板有中文内容)
/dir2
b.html
那么
mylookup = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8")
mytemplate = Template(filename="a.html", lookup=mylookup) # 提示找不到a.html文件
mytemplate = Template(filename="/dir1/a.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 提示找不到模板c.html,lookup没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup) # 出错,提示编码出错,也就是说input_encoding没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup, input_encoding="utf-8") # 正确
print mytemplate.render() # 解析正确
mytemplate = Template(filename="/dir1/b.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="a.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="b.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("a.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("b.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render() # 解析正确
TemplateLookup也可以指定模板编译之后的中间文件存储路径。
mytemplate = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render()
其文件路径也是module_directory + get_template中指定的相对路径。
四、encoding
除了input_encoding之外,mako需要关注的output_encoding。在不指定输出编码时,render()输出unicode字符串。
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
u'\u4e2d\u6587\n'
输出utf-8
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xe4\xb8\xad\xe6\x96\x87\n'
输出gbk
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="gbk")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xd6\xd0\xce\xc4\n’
如果使用了render_unicode()函数输出,则output_encoding 参数被忽略
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render_unicode()
u'\u4e2d\u6587\n'
注:系统环境
OS: OS X 10.9
Python: 2.7.5
一、字符串直接渲染
mako可以直接对一段字符串进行渲染,不过这没有多少实际意义,直接用用%或字符串拼接即可,基本用于测试演示用途。
from mako.template import Template
mytemplate = Template("hello, ${name}!")
print mytemplate.render(name="jack")
如果字符串中包含了中文等内容,需要使用unicode类型:
mytemplate = Template(u"你好, ${name}!")
print mytemplate.render(name="jack")
如果不希望使用unicode类型,则需要指定特定的input_encoding
mytemplate = Template("你好, ${name}!", input_encoding="utf-8")
print mytemplate.render(name="jack")
二、单模板渲染
mako也可以载入单个模板文件进行渲染,只要指定文件的绝对路径即可
from mako.template import Template
mytemplate = Template(filename='/dir1/a.html')
print mytemplate.render()
如果a.html有特定的编码格式,也需要指定input_encoding
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8")
print mytemplate.render()
Template中还可以指定模板编译之后的中间文件存储路径,可以方便模板语法问题的调试。
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8", module_directory='/tmp')
print mytemplate.render()
注意,mako会在/tmp目录下生成/dir1/a.html.py文件,也就是说会将filename中包含的文件路径在module_directory中生成一遍。
但是如果这个模板中包含了include之类的mako语法,这种载入方式就会因找不到文件而报错。
三、模板lookup
在mako最常用的还是TemplateLookup,为mako指定一个模板文件存放路径,而将模板查找工作交给mako处理。
from mako.template import Template
from mako.lookup import TemplateLookup
mylookup = TemplateLookup(directories=['/dir1','/dir2' ])
mytemplate = mylookup.get_template("a.html")
TemplateLookup中,可以指定多个路径,mako按顺序逐个查找,直到找到模板文件。使用了TemplateLookup之后,建议使用get_template函数得到模板。Template类中尽管可以指定lookup参数,但其他参数无法从lookup中取得预先设定的信息,会引起很多错误。
如有一下目录结构:
/dir1
a.html(模板中include了c.html)
c.html(模板有中文内容)
/dir2
b.html
那么
mylookup = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8")
mytemplate = Template(filename="a.html", lookup=mylookup) # 提示找不到a.html文件
mytemplate = Template(filename="/dir1/a.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 提示找不到模板c.html,lookup没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup) # 出错,提示编码出错,也就是说input_encoding没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup, input_encoding="utf-8") # 正确
print mytemplate.render() # 解析正确
mytemplate = Template(filename="/dir1/b.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="a.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="b.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("a.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("b.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render() # 解析正确
TemplateLookup也可以指定模板编译之后的中间文件存储路径。
mytemplate = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render()
其文件路径也是module_directory + get_template中指定的相对路径。
四、encoding
除了input_encoding之外,mako需要关注的output_encoding。在不指定输出编码时,render()输出unicode字符串。
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
u'\u4e2d\u6587\n'
输出utf-8
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xe4\xb8\xad\xe6\x96\x87\n'
输出gbk
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="gbk")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xd6\xd0\xce\xc4\n’
如果使用了render_unicode()函数输出,则output_encoding 参数被忽略
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render_unicode()
u'\u4e2d\u6587\n'
注:系统环境
OS: OS X 10.9
Python: 2.7.5
Subscribe to:
Posts (Atom)