Jinja2的“递归”标签是如何运作的?
我正在尝试用jinja2写一个非常简单的树形遍历模板,使用一些自定义对象,并重载了一些特殊方法(比如getattr、getitem等)。这看起来很简单,树的等效Python遍历也能正常工作,但我不太明白Jinja的递归是怎么回事。下面是代码:
from jinja2 import Template
class Category(object):
def __init__(self, name):
self.name = name
self.items = {}
self.children = True
def __iter__(self):
return iter(self.items)
def add(self, key, item):
self.items[key] = item
return item
def __getitem__(self, item):
return self.items[item]
def __getattr__(self, attr):
try:
return self.items[attr]
except KeyError:
raise AttributeError(attr)
def __str__(self):
return "<Category '%s'>" % self.name
template = '''
<saved_data>
{% for key in category recursive %}
{% set item = category[key] %}
{% if item.children %}
<category name="{{key}}">
{{ loop(item) }}
</category>
{% else %}
<item name="{{ key }}" value="{{ item }}" />
{% endif %}
{% endfor %}
</saved_data>
'''
b = Category('root')
c = b.add("numbers", Category('numbers'))
c.add("one", 1)
c.add("two", 2)
c.add("three", 3)
d = b.add("letters", Category('letters'))
d.add('ay','a')
d.add('bee','b')
d.add('cee','c')
e = d.add("bools", Category('bools'))
e.add('tru', True)
e.add('fals', False)
def walk(c, depth=0):
for key in c:
item = c[key]
print (' '*depth) + str(item)
if hasattr(item, 'children'):
walk(item, depth+3)
print "Python walking the tree:"
walk(b)
print ""
print "Jinja2 Walking the tree:"
t = Template(template)
print t.render(category = b)
这个模板抛出了一个异常,感觉好像递归根本没有发生。内部调用是有的,但不知怎么的,'category'的引用仍然指向父级。到底是怎么回事呢?我一定是漏掉了关于这些递归模板工作原理的一些基本知识。(或者我做了一些非常基础的傻事,但我就是看不出来。)
1 个回答
8
从你的代码来看,你对递归的理解基本正确,只有一点需要注意:在for语句中,它确实替换了可迭代对象,但并没有更新最开始用到的变量(在你的代码中是category
)。所以,你的嵌套循环在遍历子项时,set
标签查找的是原来的category
,而不是传递给loop()
的那个。
我建议把__iter__()
方法改成返回self.items.iteritems()
,然后模板改成:
<saved_data>
{% for key, item in category recursive %}
{% if item.children %}
<category name="{{key}}">
{{ loop(item) }}
</category>
{% else %}
<item name="{{ key }}" value="{{ item }}" />
{% endif %}
{% endfor %}
</saved_data>