Mako 模板系统文档翻译(3) 函数定义

翻译:木野狐 http://rchen.cnblogs.com/
原文: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>

usernameaccountdata 两个变量将会显示在主模板的 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 函数的方式来实现。

上一节: 语法 | 下一节: 运行时环境

posted on 2007-06-15 16:10  NeilChen  阅读(3616)  评论(1编辑  收藏  举报

导航