2010-08-14 124 views
12

Tengo dos lazos, ambos con dignidad. Me gustaría tener un contador incrementado durante cada iteración interna.¿Cómo puedo hacer un contador simple con las plantillas de Jinja2?

Por ejemplo, considere esta plantilla:

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() 

No debería esta impresión a través count=0count=8? No, no es así.

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 

¿Qué ofrece?

Nota: No puedo simplemente guardar el loop la variable externa para calcular el mostrador porque, en mi software, el número de iteraciones internas es variable.

+1

Tal vez estoy experimentando este error, abierto hace 12 horas: http://dev.pocoo.org/projects/jinja/ticket/389 –

+0

informé que error. El ejemplo en ella puede ser más claro. Puedo encontrar formas de evitar el problema, pero todavía creo que es contraintuitivo y algo molesto. –

Respuesta

14

Con tamaños de los grupos internos variables, esto va a funcionar:

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) 

... que imprime:

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 

supongo variables declaradas fuera de más de un nivel de alcance ca no ser asignado o algo así.

3

Parece un error, pero ¿qué hay de mover parte de ese cálculo fuera de la plantilla?

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)) 

Salida:

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 
+0

Esta es una buena idea, pero mencioné que no conozco el contenido del ciclo interno de antemano. Ver mi respuesta ... –

+0

¿Entonces no lo sabes cuando estás en el código de Python? ¿De dónde viene el bucle interno? Pero, en cualquier caso, creo que la respuesta de un nerd pagado resuelve el problema. –

3

Para resolver casos de uso como este, escribí un pequeño filtro de entorno que cuenta las ocurrencias de una clave.

Aquí es de código (con la prueba doc) de 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()  

Configuración de su entorno de registrar nuestro filtro personalizado:

#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) 

Y en su plantilla, lo utilizan como esto:

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

... que imprime:

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 
+0

¡Eso es genial, gracias! – imiric

1

Existe la función global integrada cycler() que proporciona ciclos de valores independientes del ciclo.Usando la misma idea puede definir su propia función counter() así:

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

Aquí es la clase que implementa la función:

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 

Y aquí es cómo usarlo:

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

Se va a procesar:

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

No es necesario agregar un contador. Puede acceder índice del bucle externo como esto:

{% 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 -%} 
Cuestiones relacionadas