2010-08-14 9 views
12

J'ai deux boucles, toutes deux dans la dignité. J'aimerais avoir un compteur incrémenté lors de chaque itération interne.Comment puis-je faire un simple compteur avec des modèles Jinja2?

Par exemple, considérons ce modèle:

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

Si pas l'impression count=0 par count=8? Non, ce n'est pas le cas.

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 

Ce qui donne?

Note: Je ne peux pas simplement sauver la variable loop externe pour calculer le compteur parce que, dans mon logiciel, le nombre d'itérations internes est variable.

+1

Peut-être que je rencontre ce bug, ouvert il y a 12 heures: http://dev.pocoo.org/projects/jinja/ticket/389 –

+0

j'ai signalé que punaise. L'exemple peut être plus clair. Je peux trouver des moyens d'éviter le problème, mais je pense toujours que c'est contre-intuitif et plutôt ennuyeux. –

Répondre

14

Avec la taille des groupes internes variables, cela fonctionnera:

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) 

... qui 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 

Je suppose que les variables déclarées à l'extérieur de plus d'un niveau de portée ca ne pas être affecté à ou quelque chose.

3

Cela ressemble à un bug, mais que diriez-vous de déplacer certains de ces calculs en dehors du modèle?

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

Sortie:

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

C'est une bonne idée, mais j'ai mentionné que je ne connaissais pas le contenu de la boucle interne au préalable. Voir ma réponse .... –

+0

Donc, vous ne le savez pas quand vous êtes dans le code Python? D'où vient la boucle interne? Mais en tout cas, je pense que la réponse d'un nerd payé résout le problème. –

3

Pour résoudre des cas d'utilisation comme celui-ci, j'ai écrit un petit filtre d'environnement qui compte les occurrences d'une clé.

Voilà de code (avec test 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()  

Configurez votre environnement enregistrer notre filtre personnalisé:

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

Et sur votre modèle, l'utiliser comme ceci:

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

... qui 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

C'est vraiment cool, merci! – imiric

1

Il existe une fonction globale intégrée cycler() fournissant un cycle de valeur indépendant de la boucle.En utilisant la même idée que vous pouvez définir votre propre fonction counter() comme ceci:

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

Voici la classe qui implémente la fonction:

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 

Et voici comment l'utiliser:

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

Il va rendre:

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

Pas besoin d'ajouter un compteur. Vous pouvez accéder à l'index de boucle externe comme ceci:

{% 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 -%} 
Questions connexes