如何使用 Jinja2 模板制作一个简单的计数器?

发布于 2024-09-14 23:47:21 字数 707 浏览 11 评论 0原文

我有两个 for 循环,两者在尊严上都是相似的。我希望在每次内部迭代期间增加一个计数器。

例如,考虑这个模板:

from jinja2 import Template

print Template("""
{% set count = 0 -%}
{% for i in 'a', 'b', 'c' -%}
  {% for j in 'x', 'y', 'z' -%}
    i={{i}}, j={{j}}, count={{count}}
    {% set count = count + 1 -%}
  {% endfor -%}
{% endfor -%}
""").render()

这不应该打印 count=0count=8 吗? 不,没有。

i=a, j=x, count=0
i=a, j=y, count=1
i=a, j=z, count=2
i=b, j=x, count=0
i=b, j=y, count=1
i=b, j=z, count=2
i=c, j=x, count=0
i=c, j=y, count=1
i=c, j=z, count=2

有什么作用?

注意:我不能简单地保存外部循环变量来计算计数器,因为在我的软件中,内部迭代的次数是可变的。

I have two for loops, both alike in dignity. I'd like to have a counter incremented during each inner iteration.

For example, consider this template:

from jinja2 import Template

print Template("""
{% set count = 0 -%}
{% for i in 'a', 'b', 'c' -%}
  {% for j in 'x', 'y', 'z' -%}
    i={{i}}, j={{j}}, count={{count}}
    {% set count = count + 1 -%}
  {% endfor -%}
{% endfor -%}
""").render()

Shouldn't this print count=0 through count=8? Nope, it doesn't.

i=a, j=x, count=0
i=a, j=y, count=1
i=a, j=z, count=2
i=b, j=x, count=0
i=b, j=y, count=1
i=b, j=z, count=2
i=c, j=x, count=0
i=c, j=y, count=1
i=c, j=z, count=2

What gives?

Note: I can't simply save the outer loop variable to calculate the counter because, in my software, the number of inner iterations is variable.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

心碎无痕… 2024-09-21 23:47:21

对于可变的内部组大小,这将起作用:

from jinja2 import Template

items = [
    ['foo', 'bar'],
    ['bax', 'quux', 'ketchup', 'mustard'],
    ['bacon', 'eggs'],
    ]

print Template("""
{% set counter = 0 -%}
{% for group in items -%}
  {% for item in group -%}
    item={{ item }}, count={{ counter + loop.index0 }}
  {% endfor -%}
  {% set counter = counter + group|length %}
{% endfor -%}
""").render(items=items)

...打印:

item=foo, count=0
  item=bar, count=1

item=bax, count=2
  item=quux, count=3
  item=ketchup, count=4
  item=mustard, count=5

item=bacon, count=6
  item=eggs, count=7

我猜在超过一级范围之外声明的变量不能分配给或其他东西。

With variable inner group sizes, this will work:

from jinja2 import Template

items = [
    ['foo', 'bar'],
    ['bax', 'quux', 'ketchup', 'mustard'],
    ['bacon', 'eggs'],
    ]

print Template("""
{% set counter = 0 -%}
{% for group in items -%}
  {% for item in group -%}
    item={{ item }}, count={{ counter + loop.index0 }}
  {% endfor -%}
  {% set counter = counter + group|length %}
{% endfor -%}
""").render(items=items)

...which prints:

item=foo, count=0
  item=bar, count=1

item=bax, count=2
  item=quux, count=3
  item=ketchup, count=4
  item=mustard, count=5

item=bacon, count=6
  item=eggs, count=7

I guess variables declared outside up more than one level of scope can't be assigned to or something.

如果没结果 2024-09-21 23:47:21

它看起来确实像一个错误,但是将一些计算移到模板之外怎么样?

from jinja2 import Template

outer_items = list(enumerate("a b c".split()))
inner_items = list(enumerate("x y z".split()))

print Template("""
{% for outer, i in outer_items -%}
  {% for inner, j in inner_items -%}
  {% set count = outer * num_outer + inner -%}
    i={{i}}, j={{j}}, count={{count}}
  {% endfor -%}
{% endfor -%}
""").render(outer_items=outer_items,
            inner_items=inner_items,
            num_outer=len(outer_items))

输出:

i=a, j=x, count=0
  i=a, j=y, count=1
  i=a, j=z, count=2
  i=b, j=x, count=3
  i=b, j=y, count=4
  i=b, j=z, count=5
  i=c, j=x, count=6
  i=c, j=y, count=7
  i=c, j=z, count=8

It does look like a bug, but how about moving some of that calculation outside the template?

from jinja2 import Template

outer_items = list(enumerate("a b c".split()))
inner_items = list(enumerate("x y z".split()))

print Template("""
{% for outer, i in outer_items -%}
  {% for inner, j in inner_items -%}
  {% set count = outer * num_outer + inner -%}
    i={{i}}, j={{j}}, count={{count}}
  {% endfor -%}
{% endfor -%}
""").render(outer_items=outer_items,
            inner_items=inner_items,
            num_outer=len(outer_items))

Output:

i=a, j=x, count=0
  i=a, j=y, count=1
  i=a, j=z, count=2
  i=b, j=x, count=3
  i=b, j=y, count=4
  i=b, j=z, count=5
  i=c, j=x, count=6
  i=c, j=y, count=7
  i=c, j=z, count=8
牵强ㄟ 2024-09-21 23:47:21

为了解决这样的用例,我编写了一个小型环境过滤器来计算某个键的出现次数。

这是 myfilters.py 的代码(带有文档测试):

#coding: utf-8
from collections import defaultdict

from jinja2 import environmentfilter
from jinja2.utils import soft_unicode

@environmentfilter
def inc_filter(env, key, value=1, result='value', reset=False):
    """
    Count ocurrences of key.
    Stores the counter on Jinja's environment.
        >>> class Env: pass
        >>> env = Env()
        >>> inc_filter(env, 'x')
        1
        >>> inc_filter(env, 'x')
        2
        >>> inc_filter(env, 'y')
        1
        >>> inc_filter(env, 'x')
        3
        >>> inc_filter(env, 'x', reset=True)
        1
        >>> inc_filter(env, 'x')
        2
        >>> inc_filter(env, 'x', value=0, reset=True)
        0
        >>> inc_filter(env, 'x', result=None)
        >>> inc_filter(env, 'x', result=False)
        u''
        >>> inc_filter(env, 'x', result='key')
        'x'
        >>> inc_filter(env, 'x')
        4
    """
    if not hasattr(env, 'counters'):
        env.counters = defaultdict(int)

    if reset:
        env.counters[key] = 0

    env.counters[key] += value

    if result == 'key':
        return key
    elif result == 'value':
        return env.counters[key]
    elif result == None:
        return None
    else:
        return soft_unicode('')


## Module doctest
if __name__ == '__main__':
    import doctest
    doctest.testmod()    

设置您的环境注册我们的自定义过滤器:

#coding: utf-8
from jinja2 import Environment, FileSystemLoader
from myfilters import inc_filter

env = Environment(loader=loader=FileSystemLoader('path'))
env.filters['inc'] = inc_filter

t = env.get_template('yourtemplate.txt')

items = [
    ['foo', 'bar'],
    ['bax', 'quux', 'ketchup', 'mustard'],
    ['bacon', 'eggs'],
    ]

res = t.render(items=items)

在您的模板上,像这样使用它:

{% for group in items -%}
  {% for item in group -%}
    item={{ item }}, count={{ 'an_identifier'|inc }}
  {% endfor -%}
{% endfor -%}

...打印:

item=foo, count=0
  item=bar, count=1

item=bax, count=2
  item=quux, count=3
  item=ketchup, count=4
  item=mustard, count=5

item=bacon, count=6
  item=eggs, count=7

To solve use cases like this one, I wrote a small environment filter that counts occurences of a key.

Here's de code (with doc test) of myfilters.py:

#coding: utf-8
from collections import defaultdict

from jinja2 import environmentfilter
from jinja2.utils import soft_unicode

@environmentfilter
def inc_filter(env, key, value=1, result='value', reset=False):
    """
    Count ocurrences of key.
    Stores the counter on Jinja's environment.
        >>> class Env: pass
        >>> env = Env()
        >>> inc_filter(env, 'x')
        1
        >>> inc_filter(env, 'x')
        2
        >>> inc_filter(env, 'y')
        1
        >>> inc_filter(env, 'x')
        3
        >>> inc_filter(env, 'x', reset=True)
        1
        >>> inc_filter(env, 'x')
        2
        >>> inc_filter(env, 'x', value=0, reset=True)
        0
        >>> inc_filter(env, 'x', result=None)
        >>> inc_filter(env, 'x', result=False)
        u''
        >>> inc_filter(env, 'x', result='key')
        'x'
        >>> inc_filter(env, 'x')
        4
    """
    if not hasattr(env, 'counters'):
        env.counters = defaultdict(int)

    if reset:
        env.counters[key] = 0

    env.counters[key] += value

    if result == 'key':
        return key
    elif result == 'value':
        return env.counters[key]
    elif result == None:
        return None
    else:
        return soft_unicode('')


## Module doctest
if __name__ == '__main__':
    import doctest
    doctest.testmod()    

Setup your environment registering our custom filter:

#coding: utf-8
from jinja2 import Environment, FileSystemLoader
from myfilters import inc_filter

env = Environment(loader=loader=FileSystemLoader('path'))
env.filters['inc'] = inc_filter

t = env.get_template('yourtemplate.txt')

items = [
    ['foo', 'bar'],
    ['bax', 'quux', 'ketchup', 'mustard'],
    ['bacon', 'eggs'],
    ]

res = t.render(items=items)

And on your template, use it like this:

{% for group in items -%}
  {% for item in group -%}
    item={{ item }}, count={{ 'an_identifier'|inc }}
  {% endfor -%}
{% endfor -%}

...which prints:

item=foo, count=0
  item=bar, count=1

item=bax, count=2
  item=quux, count=3
  item=ketchup, count=4
  item=mustard, count=5

item=bacon, count=6
  item=eggs, count=7
半世晨晓 2024-09-21 23:47:21

有内置的全局函数 cycler() 提供与循环无关的值循环。使用相同的想法,您可以定义自己的 counter() 函数,如下所示:

env=Environment(...) # create environment
env.globals['counter']=_Counter # define global function
env.get_template(...).render(...) # render template

这是实现该函数的类:

class _Counter(object):
  def __init__(self, start_value=1):
    self.value=start_value

  def current(self):
    return self.value

  def next(self):
    v=self.value
    self.value+=1
    return v

下面是如何使用它:

{% set cnt=counter(5) %}
item #{{ cnt.next() }}
item #{{ cnt.next() }}
item #{{ cnt.next() }}
item #{{ cnt.next() }}

它将呈现:

item #5
item #6
item #7
item #8

There is builtin global function cycler() providing loop-independent value cycling. Using the same idea you can define your own counter() function like this:

env=Environment(...) # create environment
env.globals['counter']=_Counter # define global function
env.get_template(...).render(...) # render template

Here is the class that implements the function:

class _Counter(object):
  def __init__(self, start_value=1):
    self.value=start_value

  def current(self):
    return self.value

  def next(self):
    v=self.value
    self.value+=1
    return v

And here is how to use it:

{% set cnt=counter(5) %}
item #{{ cnt.next() }}
item #{{ cnt.next() }}
item #{{ cnt.next() }}
item #{{ cnt.next() }}

It is gonna render:

item #5
item #6
item #7
item #8
我是男神闪亮亮 2024-09-21 23:47:21

从 Jinja 版本 2.10 开始,现在有一个使用命名空间的更简单的解决方案:

from jinja2 import Template

print(Template("""
{% set ns = namespace(count = 0) -%}
{% for i in 'a', 'b', 'c' -%}
  {% for j in 'x', 'y', 'z' -%}
    i={{i}}, j={{j}}, count={{ns.count}}
    {% set ns.count = ns.count + 1 -%}
  {% endfor -%}
{% endfor -%}
""").render())

结果:

i=a, j=x, count=0
i=a, j=y, count=1
i=a, j=z, count=2
i=b, j=x, count=3
i=b, j=y, count=4
i=b, j=z, count=5
i=c, j=x, count=6
i=c, j=y, count=7
i=c, j=z, count=8

参见 模板设计器文档 ->|类 jinja-globals.namespace(...)

Since Jinja version 2.10 there is now a simpler solution using namespace:

from jinja2 import Template

print(Template("""
{% set ns = namespace(count = 0) -%}
{% for i in 'a', 'b', 'c' -%}
  {% for j in 'x', 'y', 'z' -%}
    i={{i}}, j={{j}}, count={{ns.count}}
    {% set ns.count = ns.count + 1 -%}
  {% endfor -%}
{% endfor -%}
""").render())

Result:

i=a, j=x, count=0
i=a, j=y, count=1
i=a, j=z, count=2
i=b, j=x, count=3
i=b, j=y, count=4
i=b, j=z, count=5
i=c, j=x, count=6
i=c, j=y, count=7
i=c, j=z, count=8

see Template Designer Documentation ->| class jinja-globals.namespace(...)

云柯 2024-09-21 23:47:21

无需添加计数器。您可以像这样访问外循环的索引:

{% for i in 'a', 'b', 'c' -%}
  {% set outerloop = loop %}
  {% for j in 'x', 'y', 'z' -%}
    i={{i}}, j={{j}}, count={{outerloop.index0 * loop|length + loop.index0}}
  {% endfor -%}
{% endfor -%}

No need to add a counter. You can access the outer loop's index like this:

{% for i in 'a', 'b', 'c' -%}
  {% set outerloop = loop %}
  {% for j in 'x', 'y', 'z' -%}
    i={{i}}, j={{j}}, count={{outerloop.index0 * loop|length + loop.index0}}
  {% endfor -%}
{% endfor -%}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文