2010-08-14 25 views
12

Ho due cicli, entrambi uguali in dignità. Mi piacerebbe avere un contatore incrementato durante ogni iterazione interna.Come posso creare un semplice contatore con i modelli Jinja2?

Ad esempio, si consideri questo modello:

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

Non dovrebbe questa stampa count=0 attraverso count=8? No, non è così.

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 

Cosa dà?

Nota: Non posso semplicemente salvare il loop variabile esterna per calcolare il contatore perché, nel mio software, il numero di iterazioni interne è variabile.

+1

Forse sto vivendo questo bug, aperto 12 ore fa: http://dev.pocoo.org/projects/jinja/ticket/389 –

+0

mi ha riferito che bug. L'esempio in esso può essere più chiaro. Posso inventare dei modi per evitare il problema, ma continuo a pensare che sia contro-intuitivo e piuttosto fastidioso. –

risposta

14

Con gruppi di dimensioni interne variabili, questo funzionerà:

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) 

... che stampa:

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 

immagino variabili dichiarate al di fuori di più di un livello di scope ca non essere assegnato ao qualcosa

3

Sembra un bug, ma che ne dici di spostare alcuni di questi calcoli al di fuori del modello?

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

uscita:

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

Questa è una buona idea, ma ho detto che non conosco il contenuto del ciclo interno in anticipo. Vedi la mia risposta .... –

+0

Quindi non lo sai quando sei nel codice Python? Da dove viene il ciclo interno? Ma in ogni caso, penso che la risposta di un secchione pagato risolva il problema. –

3

Per risolvere casi di utilizzo come questo, ho scritto un piccolo filtro dell'ambiente che conta le occorrenze di una chiave.

Ecco de code (con test di doc) di 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()  

Imposta l'ambiente di registrazione il nostro filtro personalizzato:

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

E sul modello, usarlo in questo modo:

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

... che stampa:

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

È davvero fantastico, grazie! – imiric

1

La funzione globale incorporata è cycler() che fornisce un ciclo di valori indipendente dal ciclo.Utilizzando la stessa idea si può definire la propria funzione counter() come questo:

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

Qui è la classe che implementa la funzione:

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 

Ed ecco come usarlo:

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

Renderà:

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

Non è necessario aggiungere un contatore. È possibile accedere l'indice del ciclo esterno come questo:

{% 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 -%} 
Problemi correlati