python français - En parcourant deux listes dans les modèles Django





tutorial apprendre (6)


Vous pouvez utiliser zip dans votre vue:

mylist = zip(list1, list2)
return render_to_response('template.html', {'list': list, ... })

et dans votre modèle utiliser

{% for item1, item2 in mylist %}

pour parcourir les deux listes.

Cela devrait fonctionner avec toutes les versions de Django.

Je veux faire l'itération de la liste ci-dessous dans les modèles de django:

foo = ['foo', 'bar'];
moo = ['moo', 'loo'];

for (a, b) in zip(foo, moo):
    print a, b

code django:

{%for a, b in zip(foo, moo)%}
  {{a}}
  {{b}}
{%endfor%}

J'obtiens l'erreur ci-dessous lorsque j'essaie ceci:

File "/base/python_lib/versions/third_party/django-0.96/django/template/defaulttags.py", line 538, in do_for
    raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents

Comment puis-je accomplir cela?




On modifie ici {% for%} templatetag qui permet d'itérer plusieurs listes une fois les iziping avant:

import re

from itertools import izip
from django import template
from django.template.base import TemplateSyntaxError
from django.template.defaulttags import ForNode

register = template.Library()


class ZipExpression(object):
    def __init__(self, var):
        self.var = var

    def resolve(self, *args, **kwargs):
        return izip(*(
            f.resolve(*args, **kwargs) for f in self.var
        ))


@register.tag('for')
def do_for(parser, token):
    """
    For tag with ziping multiple iterables.
    """
    bits = token.contents.split()
    if len(bits) < 4:
        raise TemplateSyntaxError("'foreach' statements should have at least"
                                  " four words: %s" % token.contents)

    is_reversed = False
    try:
        in_index = bits.index('in')
        sequence = bits[in_index+1:]
        if sequence[-1] == 'reversed':
            is_reversed = True
            sequence.pop()
        if not sequence or 'in' in sequence:
            raise ValueError
        sequence = re.split(r' *, *', ' '.join(sequence))
    except ValueError:
        raise TemplateSyntaxError(
            "'foreach' statements should use the format"
            " 'foreach a,b,(...) in x,y,(...)': %s" % token.contents)

    loopvars = re.split(r' *, *', ' '.join(bits[1:in_index]))
    for var in loopvars:
        if not var or ' ' in var:
            raise TemplateSyntaxError("'foreach' tag received an invalid"
                                      " argumewnt: %s" % token.contents)

    if len(sequence) > 1:
        sequence = ZipExpression(map(parser.compile_filter, sequence))
    else:
        sequence = parser.compile_filter(sequence[0])

    nodelist_loop = parser.parse(('empty', 'endfor',))
    token = parser.next_token()
    if token.contents == 'empty':
        nodelist_empty = parser.parse(('endfor',))
        parser.delete_first_token()
    else:
        nodelist_empty = None
    return ForNode(
        loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)

Enregistrez-le simplement en tant que bibliothèque templatetag et importez-le dans votre modèle. Il remplacera le tag intégré {% for%} (ne vous inquiétez pas, il est compatible avec ce dernier).

Exemple d'utilisation:

{% for a,b in foo, moo %}
    {{ a }}
    {{ b }}
{% endfor %}



Vous pouvez définir les propriétés des objets foo des objets moo du côté serveur.

for f, b in zip(foo, bar):
    f.foosBar = b

context = {
    "foo": foo
}

Ceci est particulièrement propre lorsque la seconde liste est la propriété du premier (ce qui est généralement le cas).

users = User.objects.all()
for user in users:
    user.bestFriend = findBestFriendForUser(user)

context = {
    "users": users
}



Il est possible de faire

{% for ab in mylist %}
    {{ab.0}}
    {{ab.1}}
{% endfor %}

mais vous ne pouvez pas appeler à zip dans la structure for . Vous devrez d'abord stocker la liste compressée dans une autre variable, puis la parcourir.




Définissez simplement zip comme un filtre modèle :

@register.filter(name='zip')
def zip_lists(a, b):
  return zip(a, b)

Ensuite, dans votre modèle:

{%for a, b in first_list|zip:second_list %}
  {{a}}
  {{b}}
{%endfor%}



Si vous voulez aplatir une structure de données où vous ne savez pas à quelle profondeur elle est imbriquée, vous pouvez utiliser iteration_utilities.deepflatten 1

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> l = [[1, 2, 3], [4, [5, 6]], 7, [8, 9]]
>>> list(deepflatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

C'est un générateur, vous devez donc convertir le résultat en list ou l'itérer explicitement.

Pour n'aplatir qu'un seul niveau et si chacun des éléments est lui-même itérable, vous pouvez également utiliser iteration_utilities.flatten qui n'est en soi qu'une mince couche autour de itertools.chain.from_iterable :

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Juste pour ajouter quelques timings (basé sur la réponse de Nico Schlömer qui n'incluait pas la fonction présentée dans cette réponse):

Il s’agit d’un graphique log-log qui tient compte de la vaste gamme de valeurs couvertes. Pour un raisonnement qualitatif: Plus c'est bas, mieux c'est.

Les résultats montrent que si l'itérable ne contient que quelques iterables internes, alors sum sera le plus rapide. Cependant, pour de longues itertools.chain.from_iterable , seul itertools.chain.from_iterable , iteration_utilities.deepflatten ou la compréhension imbriquée ont des performances raisonnables, itertools.chain.from_iterable étant le plus rapide. (comme déjà remarqué par Nico Schlömer).

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

def flatten(items):
    """Yield items from any nested iterable; see REF."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1 Disclaimer: je suis l'auteur de cette bibliothèque





python django django-templates