手头的项目是使用DJango开发的,不过因为需要处理大量的文本,所以才用了jinja2作为部分配置文件生成的模板引擎。一般也就简单地使用:

1
2
3
from jinja2 import Template
t = Template( configure_tpl )
    return t.render(value=bala, value2=bala, ...)

今天突然需要使用jinja2中的filter(过滤器,理解为函数),于是跑去翻jinja2的文档。看了大段大段的,依旧一头雾水。神马Environment,Loader,get_template(),我根本就不需要这么复杂的功能嘛!而文档里的举例也是只言片语,常常就说这样可以添加filter:

1
2
3
def my_filter(value):
    return bala(value)
env.filters['my_filter'] = my_filter

可是,这根本就不说env变量如何弄来的!!!而且又如何生成我的configure_tpl文件?都是木有上下文的,要自己去猜……

后来索性去翻看jinja2库里对Template的实现,终于发现一个特别有用的函数:env.from_string(),顿时泪流满面……

所以,其实添加filter的最简单、完整的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
#-*- coding: UTF-8 -*-
# 替代原来的 jinja2.Template(tpl).reander()
import jinja2
def my_filter(value):
    return unicode(value) + "_nice"

env = jinja2.Environment()
env.filters['my_filter'] = my_filter
tpl = "{{ val|my_filter }}"
template = env.from_string(tpl)
print template.render(val="hello")

接着来外番,讨论一下REST式的模板语言。还是针对jinja2库,以前生成出一个配置是需要一段较长的代码来查询数据库的内容的,然后保存为一个字符串丢给jinja2替换模板。这样导致每个配置文件都需要写各种代码,而且这些个代码还是类似却不相同。而其实套用流行的思想,这些配置文件与数据库的数据大多是一一对应的,可以认为是数据资源的存储、表现形式不一样而已。因此其实可以使用较为通用的代码/函数来封装这功能:

1
data = get_workers("china", "beijing", "pku")

同时还要将查询出来的数据弄成各种格式:

1
value = to_json(data)

表现在jinja2模板里,如果使用过滤器来实现的话,就是这样:

1
{{ database|get_country:"china"|get_city:"beijing"|get_school:"pku"|to_json }}

漫长的链式。从代码封装上来看,已经达到我的目标了。不过还需要考虑一个实际的问题:上面这样的模板会有各种人来更改,既有懂行的老同学,也有新来的实习生。上面这种乱乱的代码是很容易就导致错误的。而如果要封装得简约简单,那么首推就是REST风格了:

1
{{ database.china.bejing.pku.to_json() }}

对比前一种,整齐顺眼N倍,可维护性也高了很多。

REST风格的模板变量的实现,利用的是python多数模板库的一个常见的处理流程:所有的 value.abc 取值的时候,通常会变为对value取属性值abc。因此,实现__getattr__(self, key)方法,就能够捕抓这种属性值的访问(当然这里捕抓的是abc这种”本不存在”的属性),记录下来,然后在某个时候进行实际的查询处理。(这个也算是附带的【延时】操作效果)

示例代码放在了github上rest-template.py。可能需要注意的是,有可能REST路径中有纯数字的,例如

1
{{ phones.china.10086.email }}

对于纯数字,它是不可能为属性的,因此模板库会调用__getitem__(self,key)来查询,所以也需要写这个方法。

PS:REST风格的python函数库也有许多,典型代表则是BeautifulSoup库,它对各种tag的读取都是REST风格的。

,