Как я могу сделать простой счетчик с шаблонами Jinja2?
У меня есть два для петель, оба одинаково достойны. Я бы хотел, чтобы счетчик увеличивался во время каждой внутренней итерации.
Например, рассмотрим этот шаблон:
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=0
через count=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
Что дает?
Примечание. Я не могу просто сохранить внешнюю переменную loop
для вычисления счетчика, потому что в моем программном обеспечении количество внутренних итераций является переменной.
Ответы
Ответ 1
С переменными размерами внутренних групп это будет работать:
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
Я предполагаю, что переменные, объявленные вне более чем одного уровня области, не могут быть назначены или что-то в этом роде.
Ответ 2
Это похоже на ошибку, но как перемещать некоторые из этих вычислений вне шаблона?
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
Ответ 3
Чтобы решить такие варианты использования, как этот, я написал небольшой фильтр среды, который учитывает вхождения ключа.
Здесь код (с doc-тестом) 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 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
Ответ 4
Существует встроенная глобальная функция 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
Ответ 5
Не нужно добавлять счетчик. Вы можете получить доступ к индексу внешнего цикла следующим образом:
{% 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 -%}