Sunday, January 12, 2014

mako二三事 -- Loop Context

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....来访问。

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实例