Jinja2的“递归”标签是如何运作的?

10 投票
1 回答
8230 浏览
提问于 2025-04-15 15:03

我正在尝试用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>

撰写回答