Mako 模板系统文档翻译(3) 函数定义
原文:http://www.makotemplates.org/docs/defs.html
译文:
Version: 0.1.5 Last Updated: 05/01/07 20:21:35
Defs
def 是一个简单的标签,用于给任意文本块或代码块定界。它在模板最终生成的 Python 代码里是一个可调用的函数。
<%def name="hello()">
hello world
</%def>
通常我们可以通过表达式来调用它们:
the def: ${hello()}
如果 <%def> 没有嵌套定义在另一个 <%def> 中,则称为顶层的 def, 它可以在模板的任何地方被使用,甚至可以在定义它的位置之前。
所有的 defs, 不管是不是顶层的,都可以访问当前的上下文名称空间,和模板的访问权限完全一样。设想下列模板在执行时被指定了一个包含 username 和 accountdata 变量的上下文:
Hello there ${username}, how are ya. Lets see what your account says:
${account()}
<%def name="account()">
Account for ${username}:<br/>
% for row in accountdata:
Value: ${row}<br/>
% endfor
</%def>
username 和 accountdata 两个变量将会显示在主模板的 body 中,同样也会显示在 account() def 的 body 中。
既然 defs 不过就是 python 函数,你自然也能够定义和传递参数了:
${account(accountname='john')}
<%def name="account(accountname, type='regular')">
account name: ${accountname}, type ${type}
</%def>
当你为 def 定义参数时,他们需要遵从 Python 的规定(比如,除了提供了默认值的关键字参数之外的所有参数,都需要提供)。这和上下文层次的变量不同,后者对不存在的名称会估算为 UNDEFINED 而不是出错。
从其他文件调用 defs
顶层的 <%defs> 被编译到模板对应的模块中,并且可以被从外部调用;包括从其他模板,或是普通的 Python 代码来调用。从其他模板中调用 <%def> 有点像使用 <%include> —— 差别在于,你是在调用模板中的一个函数,而不是整个模板。
远程的 <%def> 调用也有点类似于 Python 中调用另一个模块中的函数的情形。需要有一个“导入”的步骤,以便从其他模板中萃取出名称,添加到你自己的模板中;然后这些名称的函数才可以被调用。
要导入另一个模板,使用 <%namespace> 标签:
<%namespace name="mystuff" file="mystuff.html"/>
上面的标签添加了一个局部变量 "mystuff" 到当前范围中。
然后,只要调用 mystuff 中的函数即可:
${mystuff.somedef(x=5,y=7)}
<%namespace> 标签还支持类似 Python import 语句的一些其他的语义,包括将名称提取到局部变量空间中,或使用 * 来代表所有名称,使用 import 属性:
<%namespace file="mystuff.html" import="foo, bar"/>
这里只是对名称空间的概念做了一个简单的介绍,名称空间是 Mako 的一个核心概念,在文档中有独立的章节介绍。更多的细节和例子,见 Namespaces。
Defs 中的 Defs
def 模型遵循 Python 中关于闭包的一些规则。在一个 <%def> 中定义另一个 <%def>,会将它定义在父 def 的外围环境(enclosing scope) 中:
<%def name="mydef()">
<%def name="subdef()">
a sub def
</%def>
im the def, and the subcomopnent is ${subdef()}
</%def>
就象 Python 中一样,定义在内嵌 <%def> 之外的名称,在其内部一样存在:
<%
x = 12
%>
<%def name="outer()">
<%
y = 15
%>
<%def name="inner()">
inner, x is ${x}, y is ${y}
</%def>
outer, x is ${x}, y is ${y}
</%def>
在 def 内的赋值语句会创建一个 def 范围内的局部变量(再一次的和 Python 自身语法吻合)。比如下面的代码就会引发错误:
<%
x = 10
%>
<%def name="somedef()">
# error !
somedef, x is ${x}
<%
x = 27
%>
</%def>
...因为对 x 的赋值将 x 定义为了 somedef 范围内的局部变量,而试图输出“外部”版本的 x 是访问不到的。
调用方自带内容或内嵌函数的方式调用 def
def 的另一个方面是它可以带内容的进行调用。也就是,当你调用 def 时,同时定义一个块的内容(或多个块),这些块将提供给你要调用的 def. 这种调用方式的主要目的是为了创建自定义的,可嵌套的标签,就象其他模板语言的自定义标签生成系统 —— 外部的标签控制内嵌标签的执行,并且可以和它们沟通状态信息。只有在 Mako 中,你才不需要使用任何外部的 Python 模块,你可以直接在你的模板中定义可任意内嵌的标签。
为了达到这个目标,需要通过 <%call> 标签而不是常规的 ${} 语法来调用目标 def. 这样,目标 def 就会在其上下文中获得一个 caller 变量,其中包含一个名称空间,在此名称空间中包含了调用者的内容部分(body), 以及 <%call> 标签中定义的其他 defs. 而调用者的内容(body) 可通过 body() 方法来取得:
<%def name="buildtable()">
<table>
<tr><td>
${caller.body()}
</td></tr>
</table>
</%def>
<%call expr="buildtable">
I am the table body.
</%call>
这会产生下列输出 (空白已格式化):
<table>
<tr><td>
I am the table body.
</td></tr>
</table>
body() 可以被执行多次,或根本不执行。这意味着你可以使用带内容的 def 调用(def-call-with-content)来创建迭代器(iterators),条件语句等:
<%def name="lister(count)">
% for x in range(1,count):
${caller.body()}
% endfor
</%def>
<%call expr="lister(3)">
hi
</%call>
输出:
hi
hi
hi
一个自定义的“条件”标签:
<%def name="conditional(expr)">
% if expr:
${caller.body()}
% endif
</%def>
<%call expr="conditional(4==4)">
im the result
</%call>
输出:
im the result
但还有更精彩的。body() 函数还可以传递参数:
<%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>
<%call expr="layoutdata([[1,2,3],[4,5,6],[7,8,9]])" args="col">
Body data: ${col}
</%call>
输出(空白以格式化):[译注:似乎有误!应该是3行3列表格]
<table>
<tr>
<td>Body data: 1</td><td>Body data: 2</td><td>Body data: 3</td>
<td>Body data: 4</td><td>Body data: 5</td><td>Body data: 6</td>
<td>Body data: 7</td><td>Body data: 8</td><td>Body data: 9</td>
</tr>
</table>
你不用仅盯着调用 body() 函数,在调用方可以定义任意多个 callables,使得 <%call> 标签可定制所有布局:
<%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
<%call expr="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
</%call>
上述代码会输出(空白已格式化):
<div class="mainlayout">
<div class="header">
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>
利用 <%call> 你可以做很多事情。可以创建表单控件库(form widget libraries),比如一个自包含的 <form> 标签,以及一组内嵌的 HTML input 元素,或者用 <div> 或其他元素创建可移植的包装控件,你可以创建标签来解释数据行,比如从数据库中得到的数据,然后将行的每一列传递给 body() 的一个可调用函数,这样就可以对数据行进行任何你想要的排版。基本上,你在其他系统中通过“自定义标签”或标签库想做的事情,Mako 中都可以通过 <%def> 或通过用 <%call> 调用 Python 函数的方式来实现。