Python love: les listes en intension (partie 1)


Dans le top 10 des raisons d’aimer Python se hisse aisément les listes en intension (avec un “s”), ou “comprehension lists” pour les gens branchés. Rappel du concept, et un petit tour complet de ce qu’on peut en faire. Les connaisseurs attendront le second article qui aborde des notions avancées, et contiendra quelques bonus.

Disclaimer: pour comprendre ce petit gros article, il faut être à l’aise avec la boucle for et les listes.

La boucle for

En Python, on itère beaucoup, c’est à dire qu’on applique très souvent un traitement à tous les éléments d’une séquence, un par un. Et pour ça il y a la boucle for:

>>> sequence = ["a", "b", "c"]
>>> for element in sequence:
...     print(element)
...
a
b
c

Et très souvent, on fait une nouvelle liste avec les éléments de la première liste, mais modifiés:

>>> sequence = ["a", "b", "c"]
>>> new_sequence = []
>>> for element in sequence:
...     new_sequence.append(element.upper())
...
>>> print(new_sequence)
['A', 'B', 'C']

Les listes en intension: la base

Cette opération – prendre une séquence, modifier les éléments un par un, et faire une autre liste avec – est très commune. Et comme pour à peu près tout ce qui est opération courante, Python possède une manière élégante de le faire plus vite:

>>> sequence = ["a", "b", "c"]
>>> new_sequence = []
>>> for element in sequence:
...     new_sequence.append(element.upper())
...
>>> print(new_sequence)
['A', 'B', 'C']

Devient:

>>> sequence = ["a", "b", "c"]
>>> new_sequence = [element.upper() for element in sequence]
>>> print(new_sequence)
['A', 'B', 'C']

Il n’y a aucun mystère, ce code fait exactement la même chose, mais:

>>> new_sequence = []
>>> for element in sequence:
...     new_sequence.append(element.upper())

Est réduit à:

>>> new_sequence = [element.upper() for element in sequence]

Ne cherchez pas un truc compliqué, c’est juste une question de syntaxe, ça fait la même chose, mais écrit différemment : à droite, la boucle, à gauche, ce qu’on veut mettre dans la liste finale.

Et c’est surtout beaucoup plus court.

Là où ça devient franchement sympa, c’est que l’on peut assigner le résultat d’une liste en intension directement à la variable originale:

>>> sequence = ["a", "b", "c"]
>>> new_sequence = [element.upper() for element in sequence]
>>> print(new_sequence)
['A', 'B', 'C']

Devient alors:

>>> sequence = ["a", "b", "c"]
>>> sequence = [element.upper() for element in sequence]
>>> print(sequence)
['A', 'B', 'C']

Et vous avez du coup un moyen très propre de transformer toute une liste.

Listes en intension avancées

On peut faire bien plus avec les listes en intension. Python est un langage dynamiquement typé, donc on peut transformer carrément le type de liste.

>>> sequence = [1, 2, 3]
>>> print([str(nombre) for nombre in sequence])
['1', '2', '3']

On peut aussi faire des opérations un peu plus complexes:

>>> sequence = [1, 2, 3]
>>> print(['a' * nombre for nombre in sequence])
['a', 'aa', 'aaa']

Et même construire des sequences imbriquées à la volée:

>>> sequence = [1, 2, 3]
>>> print(list(range(5))) # petit rappel de l'usage de la fonction range
[0, 1, 2, 3, 4]
>>> sequence = [(nombre, list(range(nombre))) for nombre in sequence]
>>> print(sequence)
[(1, [0]), (2, [0, 1]), (3, [0, 1, 2])]
>>> print(sequence[-1])
(3, [0, 1, 2])
>>> print(sequence[-1][0])
3
>>> print(sequence[-1][1])
[0, 1, 2]

La syntaxe [expression for element in sequence] autorise n’importe quelle expression, du coup on peut créer des listes très élaborées, en utilisant tous les opérateurs mathématiques, logiques, etc, et toutes les fonctions que l’on veut.

Filtrer avec les listes en intension

Une autre opération courante consiste à filtrer la liste plutôt que de la transformer:

>>> nombres = range(10)
>>> nombres_pairs = []
>>> for nombre in nombres:
...     if nombre % 2 == 0: # garder uniquement les nombres pairs
...         nombres_pairs.append(nombre)
...
>>> print(nombres)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> print(nombres_pairs)
[0, 2, 4, 6, 8]

Évidement Python a également une syntaxe plus courte pour cela. Il suffit de rajouter la condition à la fin:

>>> nombres = range(10)
>>> print([nombre for nombre in nombres if nombre % 2 == 0])
[0, 2, 4, 6, 8]

Toutes les expressions habituellement utilisables pour tester une condition sont également disponibles.

Bien sûr, rien ne vous empêche de filtrer ET de transformer la liste en même temps. En clair, un nouvel arrivant à Python fera ça:

>>> nombres = range(10)
>>> sommes = []
>>> for nombre in nombres:
...     if nombre % 2 == 0:
...         somme = 0
...         for i in range(nombre):
...             somme += i
...         sommes.append(somme)
...
>>> print(sommes)
[0, 1, 6, 15, 28]

Un codeur qui trouve ses marques fera ça:

>>> sommes = []
>>> for nombre in range(10):
...     if nombre % 2 == 0:
...         sommes.append(sum(range(nombre)))
...
>>> print(sommes)
[0, 1, 6, 15, 28]

Un pythoniste affranchi ira droit au but:

>>> print([sum(range(nombre)) for nombre in range(10) if nombre % 2 == 0])

Bon, en vérité il fera plutôt:

>>> [sum(range(nombre)) for nombre in range(0, 10, 2)]

Mais c’était pour l’exemple :-)

Les listes en intension ont encore plus à offrir, donc une fois que vous êtes bien familiarisés avec la notion, lisez le second article.

28 thoughts on “Python love: les listes en intension (partie 1)

  • Sam Post author

    Apparement y a plein de monde qui vient sur cette page en cherchant:

    Comment convertir une liste en entiers en python ?

    Alors autant répondre en commentaire à cette question silencieuse:

    >>> ma_list = ['1', '2', '3', '4']
    >>> print [int(x) for x in ma_list]
    [1, 2, 3, 4]

    Et voilà, la liste de strings est convertie en liste d’entiers. Enfin en supposant que c’est ce que cherchent ces mystérieux visiteurs.

    • Max

      y a un anglophone qui voulait savoir comment convertir ‘[12,43]’ en [12,43]. Je retrouve plus son commentaire. Aidez-le les amis !

    • Max

      Y a des gens qui viennent en cherchant “salope en effort” aussi, Dois-je pour autant en faire un article ? Ne cédons pas à la démagogie kantienne, commençons par une masturbation effréné, nous ferons les présentations plus tard…

  • Sam Post author

    J’ai trashé le commentaire, pas de comment en anglais ici. Je l’ai contacté par mail pour lui filer la réponse, et je l’ai redirigé vers stackoverflow où il trouvera tout ce qu’il lui faut en english.

  • Sam Post author

    Bon, je crois que je vais foutre les réponses aux questions que les gens tapent dans les moteurs de recherche en com. Au moins il ne viendront pas pour rien.

    Imprimer les chiffres de 1 à 9 en Python

    for x in xrange(1, 10):
        print x
  • Sam Post author

    Bon évidement, le mec qui a cherché “détruire nginx” peut aller se faire foutre.

    Quand a celui qui a pondu “salope en effort”. Bon…

  • Max

    Je me demande bien ce qu’il a voulu dire par “salope en effort” …. OD…. DO ? Un musicien peut-être ?…

  • Sam Post author

    Pour le mec qui a cherché:

    prendre le max d’une liste python

    >>> l = [1, 3, 5, 2, 4]
    >>> max(l)
    5
  • JeromeJ

    Ahaha x) j’ai déjà fait ça sur mon site aussi (répondre à des questions qui avaient été posées à Google ou autre ^^) C’est vrai qu’on ne sait jamais … ça pourrait servir au prochain ou si le type repasse par là.

    Pour l’article, on voit que Python a séduit une personne de plus :) j’en suis un grand fana aussi.

    SOIT DIT EN PASSANT cependant, j’ai vu (je ne sais plus où) que, questions d’optimisation, il était préférable de continuer à utiliser filter et map dans certains cas.

    En particulier si la list comprehension pourrait être remplacée par un simple map ou filter (surtout si c’est avec une fonction déjà déclarée).

    Ainsi, les list comprehension deviennent de plus en plus intéressantes au fur et à mesure qu’on cumule leur fonctionnalités : c’est quand même un putain de combo filter lambda map lambda.

    À bon entendeur !

  • Sam Post author

    Les map/filter posent un problèmes en ooo.

    [x.strip('é') for x in chain]

    n’est pas facile à traduire.

    map(strin.strip, chain)

    ou

    map(unicode.strip, chain)

    ?

    Les deux donnent un résultat différent, alors que le premier préserve le duck typing.

  • Ahmed

    Une astuce pour utiliser des méthode avec map et de mettre une petite lambda:

    l = map(lambda e : e.upper(), l)

    Par exemple.

    Pour l’exemple des int, pas besoin de méthode:

    l = map(int, [‘1’, ‘2’, ‘3’])

  • Sam Post author

    A part, parfois, gagner quelques charactères, je ne vois plus l’interêt de garder la fonction map alors qu’on a les listes en intention.

  • DiZ

    L’usage de map est utile quand la fonction à appliquer existe déjà car Python optimisera son utilisation de la mémoire.

    En revanche, en terme de performances, l’utisation de map et des fonctions lambda est à bannir. Y préférer une ‘comprehension list’ largement optimisée, surtout depuis Python 3.x (avec une version plus antérieure, la différence sera moindre). Ou pourquoi même un générateur s’il y a juste besoin des éléments à la volée (il suffit d’échanger les [] contre des () ) !

  • Pilume

    Merci bien, j’suis tombé sur ton site en cherchant comment parser un flux rss en python. ça promet d’etre long et galere, mais grace a tes explications simples et concrètes j’espere vite progresser. bonne continuation

  • Emmanuel

    Salut, merci pour ces super articles qui donnent envie d’apprendre le python.

    Il y a une erreur dans le premier exemple “Listes en intentions avancées”.
    Il faut remplacer str(element) par str(nombre)

  • MiK

    Merci pour l’article et les commentaires utiles pour l’optimisation.

    Juste une petite correction on dit en intension par opposition à extension. Les termes viennent de la théorie des ensembles.

    Voir le wikipedia à ce sujet.

  • Anne Onyme

    Plop!

    Je vous propose de profiter de ce petit dépoussiérage d’été pour corriger les dernières fautes de frappe/d’orthographe/de grammaire, comme ça l’article sera encore plus parfait que parfait.

    Je propose:

    • “listes en intensions” -> “listes en intension” (sans ‘s’) (à corriger 2 fois);

    • “# petit rappel de l’usage de la fonction range)” -> parenthèse fermée mais non ouverte;

    • “un nouveau arrivant” -> “un nouvel arrivant”;

    • “en vérité il ferait plutôt” -> “en vérité il fera plutôt” (concordance des temps);

    • “vous êtes bien familiarisé” -> “vous êtes bien familiarisés”.

    Si mon commentaire vous fait chier, merci de l’envoyer dans /dev/nul.

  • blue

    Salut,

    J’viens de découvrir votre site et j’dois dire que l’aime beaucoup,

    vraiment.

    Juste une petite chose si je peux me permettre …

    Intentionnellement je dis beaucoup de conneries,

    et que l’on soit francophone ou anglophone, la plupart d’entre nous ne sont pas contre un petit Thé des fois …

  • Sam Post author

    Non. Intension mathématique, pas “j’ai l’intention de”.

  • blue

    Waaahhh !!!

    Mais CA-RRE-MENT.

    C’est : la définition est dans la liste,

    et pas : j’ai l’intention de retourner une liste …

    Merci les gars !

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.