Loop Context是mako中用于跟踪for循环状态的一种机制,使用好它可以在写循环时建设不少代码量。例如
<ul>
% for a in ("one", "two", "three"):
<li>Item ${loop.index}: ${a}</li>
% endfor
</ul>
将生成如下内容:
<ul>
<li>Item 0: one</li>
<li>Item 1: two</li>
<li>Item 2: three</li>
</ul>
这里的index是一个以0开始的计数。
一、Loop Context常见用法
Loop Context不仅有index技术,还包含了很多index的变种。如:
* loop.even 计数是否是偶数
* loop.odd 计数是否是奇数
* loop.first 是否是第一项
如果被循环的参数有__len__属性,那么还有:
* loop.reverse_index 反向计数,由len-1到0
* loop.last 是否是最后一项
Loop Context中一个比较特殊的用法是cycle,它能根据循环计数反复遍历一个既定的列表。举个例子:
<ul>
% for item in ('spam', 'ham', 'eggs'):
<li class="${loop.cycle('even', 'odd')}">${item}</li>
% endfor
</ul>
将生成如下内容:
<ul>
<li class="even">spam</li>
<li class="odd">ham</li>
<li class="even">eggs</li>
</ul>
这种用法在一些列表效果(如列表中行行之间背景色循环间隔)处理中非常方便。
二、多重循环
在表格生成过程中,经常会用一个for显示tr,里面在内嵌一个for 显示td。这种时候在子循环中可能需要访问父循环的计数,在一般程序里,会用类似i,j这样两个变量,在mako中,有了Loop Context,完全不需要定义这样的变量。如:
<table>
% for consonant in 'pbj':
<tr>
% for vowel in 'iou':
<td class="${'black' if (loop.parent.even == loop.even) else 'red'}”> # 父子循环技术同为奇、偶,显示black,否则显示red
${consonant + vowel}t
</td>
% endfor
</tr>
% endfor
</table>
将显示:
<table>
<tr>
<td class="black">
pit
</td>
<td class="red">
pot
</td>
<td class="black">
put
</td>
</tr>
<tr>
<td class="red">
bit
</td>
<td class="black">
bot
</td>
<td class="red">
but
</td>
</tr>
<tr>
<td class="black">
jit
</td>
<td class="red">
jot
</td>
<td class="black">
jut
</td>
</tr>
</table>
可以看的出,Loop Context可以通过loop.parent来访问上一级循环,如果有更多重的循环,则可以用loop.parent.parent....来访问。
Sunday, January 12, 2014
Friday, January 3, 2014
mako二三事 -- Context
Context是mako中的一个重要概念,负责模板与外界所有的数据传递。在模板中,我们可以用变量context来访问它。Context由两个重要组成部分组成,一个是类似StringIO的输出缓存buffer,另一个是存储参数(render参数、built-in参数等)的字典,该字典中的所有参数都可以直接在模板中使用。
from mako.template import Template
from mako.runtime import Context
from StringIO import StringIO
mytemplate = Template("hello, ${name}!")
buf = StringIO()
ctx = Context(buf, name="jack")
mytemplate.render_context(ctx)
print buf.getvalue()
1. 输出缓存
模板中所有的文本,${}包含的表达式,context.write()等方式都可直接将结果保存到Context中的输出缓存buffer中。不过尽量不要直接去操纵这个变量,因为在filter/cache等场景下中,该变量有可能会改变。
2. 参数字典
render传入模板的参数都会放入到context的参数字典中,在模板解析时,mako会判断一个变量是否已经被定义(如赋值),如果未定义,就会在模板开头从context中载入这个变量,如:
username = context.get('username', UNDEFINED)
因此,在模板中可以直接按名字引用context中存储的参数变量。
3. UNDEFINED
UNDEFINED是mako中定义的一个类mako.runtime.Undefined的一个实例,主要用于处理模板中可能会出现的未定义变量。不过因为UNDEFINED在转成字符串是是raise NameError("Undefined”),所以开发者看不出究竟是哪个变量没有定义。这给开发带来了一定的麻烦。因此,mako引入了strict_undefined参数,只要在Template、TemplateLookup中传入strict_undefined=True参数,那么mako遇到未定义变量将直接抛出NameError,如定义了strict_undefined=True之后,mako生成的代码是:
try:
mytest = context['mytest']
except KeyError:
raise NameError("'mytest' is not defined")
而不是
mytest= context.get('mytest', UNDEFINED)
不过如果希望在模板中自己去判断变量是否存在,那么就需要用类似:
% if someval is UNDEFINED:
someval is: no value
% else:
someval is: ${someval}
% endif
或
${'not defined' if someval is UNDEFINED else someval }
这种方式来判断了,这个时候就需要设置strict_undefined=False。
4. 模板之间的变量共享
前面提到了,在模板中定义的所有变量都是render_body的私有变量,因此如果需要在几个不同的模板之间(一次渲染请求中)共享某个参数,就需要使用context来进行传递了。
但上面也提到了context很有可能在一次渲染过程中,几个不同模板间会有不同,那么如何来做到变量的共享呢?
其实,我们可以在render的时候传入一个空的字典,不是None,是{},如:
output = template.render(attributes={})
接下来,我们就可以在模板中用如下方式来赋值、引用了:
<%
attributes['foo'] = 'bar'
%>
'foo' attribute is: ${attributes['foo']}
5、context常见使用方法
context是一个类似字典的数据结构,常见的一些用法如下:
* context[key] / context.get(key, default=None) 直接访问context中的参数字典
* keys() 得到context中所有参数名
* kwargs 得到context中所有参数的一份拷贝
* write() 写入到输出缓存
* lookup 得到TemplateLookup实例
from mako.template import Template
from mako.runtime import Context
from StringIO import StringIO
mytemplate = Template("hello, ${name}!")
buf = StringIO()
ctx = Context(buf, name="jack")
mytemplate.render_context(ctx)
print buf.getvalue()
1. 输出缓存
模板中所有的文本,${}包含的表达式,context.write()等方式都可直接将结果保存到Context中的输出缓存buffer中。不过尽量不要直接去操纵这个变量,因为在filter/cache等场景下中,该变量有可能会改变。
2. 参数字典
render传入模板的参数都会放入到context的参数字典中,在模板解析时,mako会判断一个变量是否已经被定义(如赋值),如果未定义,就会在模板开头从context中载入这个变量,如:
username = context.get('username', UNDEFINED)
因此,在模板中可以直接按名字引用context中存储的参数变量。
3. UNDEFINED
UNDEFINED是mako中定义的一个类mako.runtime.Undefined的一个实例,主要用于处理模板中可能会出现的未定义变量。不过因为UNDEFINED在转成字符串是是raise NameError("Undefined”),所以开发者看不出究竟是哪个变量没有定义。这给开发带来了一定的麻烦。因此,mako引入了strict_undefined参数,只要在Template、TemplateLookup中传入strict_undefined=True参数,那么mako遇到未定义变量将直接抛出NameError,如定义了strict_undefined=True之后,mako生成的代码是:
try:
mytest = context['mytest']
except KeyError:
raise NameError("'mytest' is not defined")
而不是
mytest= context.get('mytest', UNDEFINED)
不过如果希望在模板中自己去判断变量是否存在,那么就需要用类似:
% if someval is UNDEFINED:
someval is: no value
% else:
someval is: ${someval}
% endif
或
${'not defined' if someval is UNDEFINED else someval }
这种方式来判断了,这个时候就需要设置strict_undefined=False。
4. 模板之间的变量共享
前面提到了,在模板中定义的所有变量都是render_body的私有变量,因此如果需要在几个不同的模板之间(一次渲染请求中)共享某个参数,就需要使用context来进行传递了。
但上面也提到了context很有可能在一次渲染过程中,几个不同模板间会有不同,那么如何来做到变量的共享呢?
其实,我们可以在render的时候传入一个空的字典,不是None,是{},如:
output = template.render(attributes={})
接下来,我们就可以在模板中用如下方式来赋值、引用了:
<%
attributes['foo'] = 'bar'
%>
'foo' attribute is: ${attributes['foo']}
5、context常见使用方法
context是一个类似字典的数据结构,常见的一些用法如下:
* context[key] / context.get(key, default=None) 直接访问context中的参数字典
* keys() 得到context中所有参数名
* kwargs 得到context中所有参数的一份拷贝
* write() 写入到输出缓存
* lookup 得到TemplateLookup实例
Friday, December 20, 2013
mako二三事 -- Blocks二
二、命名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>等地方使用
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>等地方使用
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里面会讲到。
Subscribe to:
Posts (Atom)