Recherche…


Introduction

Une compréhension de liste est un outil syntaxique pour créer des listes de manière naturelle et concise, comme illustré dans le code suivant pour faire une liste de carrés des nombres 1 à 10: [i ** 2 for i in range(1,11)] Le dummy i d'une range liste existante est utilisé pour créer un nouveau motif d'élément. Il est utilisé là où une boucle for serait nécessaire dans des langages moins expressifs.

Syntaxe

  • [i pour i in range (10)] # compréhension de base de la liste
  • [i for i in xrange (10)] # compréhension de base de la liste avec objet générateur en python 2.x
  • [i pour i dans la plage (20) si i% 2 == 0] # avec le filtre
  • [x + y pour x dans [1, 2, 3] pour y dans [3, 4, 5]] # boucles imbriquées
  • [i si i> 6 sinon 0 pour i dans la plage (10)] # expression ternaire
  • [i si i> 4 sinon 0 pour i dans la plage (20) si i% 2 == 0] # avec filtre et expression ternaire
  • [[x + y pour x dans [1, 2, 3]] pour y dans [3, 4, 5]] # compréhension de liste imbriquée

Remarques

Les compréhensions de liste ont été décrites dans PEP 202 et introduites dans Python 2.0.

Liste conditionnelle

Étant donné une compréhension de la liste, vous pouvez ajouter une ou plusieurs conditions if pour filtrer les valeurs.

[<expression> for <element> in <iterable> if <condition>]

Pour chaque <element> dans <iterable> ; Si <condition> True , ajoutez <expression> (généralement une fonction de <element> ) à la liste renvoyée.


Par exemple, cela peut être utilisé pour extraire uniquement des nombres pairs d'une suite d'entiers:

[x for x in range(10) if x % 2 == 0]
# Out: [0, 2, 4, 6, 8]

Démo en direct

Le code ci-dessus est équivalent à:

even_numbers = [] 
for x in range(10):
    if x % 2 == 0:
        even_numbers.append(x)

print(even_numbers)
# Out: [0, 2, 4, 6, 8]

En outre, une compréhension conditionnelle de la forme [e for x in y if c] (où e et c sont des expressions en termes de x ) est équivalente à list(filter(lambda x: c, map(lambda x: e, y))) .

Malgré le même résultat, faites attention au fait que le premier exemple est presque deux fois plus rapide que le second. Pour ceux qui sont curieux, c'est une bonne explication de la raison pour laquelle.


Notez que ceci est très différent de l'expression conditionnelle ... if ... else ... (parfois appelée expression ternaire ) que vous pouvez utiliser pour la partie <expression> de la compréhension de liste. Prenons l'exemple suivant:

[x if x % 2 == 0 else None for x in range(10)]
# Out: [0, None, 2, None, 4, None, 6, None, 8, None]

Démo en direct

Ici, l'expression conditionnelle n'est pas un filtre, mais plutôt un opérateur déterminant la valeur à utiliser pour les éléments de la liste:

<value-if-condition-is-true> if <condition> else <value-if-condition-is-false>

Cela devient plus évident si vous le combinez avec d’autres opérateurs:

[2 * (x if x % 2 == 0 else -1) + 1 for x in range(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]

Démo en direct

Si vous utilisez Python 2.7, xrange peut être préférable à range pour plusieurs raisons, comme décrit dans la documentation de xrange .

[2 * (x if x % 2 == 0 else -1) + 1 for x in xrange(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]

Le code ci-dessus est équivalent à:

numbers = []
for x in range(10):
    if x % 2 == 0:
        temp = x
    else:
        temp = -1
    numbers.append(2 * temp + 1)
print(numbers)
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]

On peut combiner des expressions ternaires et if conditions. L'opérateur ternaire travaille sur le résultat filtré:

[x if x > 2 else '*' for x in range(10) if x % 2 == 0]
# Out: ['*', '*', 4, 6, 8]

La même chose n'aurait pas pu être réalisée uniquement par l'opérateur ternaire:

[x if (x > 2 and x % 2 == 0) else '*' for x in range(10)]
# Out:['*', '*', '*', '*', 4, '*', 6, '*', 8, '*']

Voir aussi: Filtres , qui offrent souvent une alternative suffisante aux compréhensions de listes conditionnelles.

Liste des compréhensions avec des boucles imbriquées

Liste compréhensions peuvent utiliser imbriqués for les boucles. Vous pouvez coder un nombre quelconque de boucles imbriquées pour l' intérieur d' une compréhension de la liste, et chaque for la boucle peut avoir une option associée if test. Ce faisant, l'ordre du for des constructions est le même ordre que lors de l' écriture d' une série d'imbriquée for les déclarations. La structure générale des compréhensions de listes ressemble à ceci:

[ expression for target1 in iterable1 [if condition1]
             for target2 in iterable2 [if condition2]...
             for targetN in iterableN [if conditionN] ]

Par exemple, le code suivant aplatit une liste de listes utilisant plusieurs for instructions:

data = [[1, 2], [3, 4], [5, 6]]
output = []
for each_list in data:
    for element in each_list:
        output.append(element)
print(output)
# Out: [1, 2, 3, 4, 5, 6]

peut être écrit comme une compréhension équivalente de liste avec de multiples for les constructions:

data = [[1, 2], [3, 4], [5, 6]]
output = [element for each_list in data for element in each_list]
print(output)
# Out: [1, 2, 3, 4, 5, 6]

Démo en direct

À la fois dans la forme développée et dans la compréhension de la liste, la boucle externe (la première pour l'instruction) vient en premier.


En plus d'être plus compact, la compréhension imbriquée est également beaucoup plus rapide.

In [1]: data = [[1,2],[3,4],[5,6]]
In [2]: def f():
   ...:     output=[]
   ...:     for each_list in data:
   ...:         for element in each_list:
   ...:             output.append(element)
   ...:     return output
In [3]: timeit f()
1000000 loops, best of 3: 1.37 µs per loop
In [4]: timeit [inner for outer in data for inner in outer]
1000000 loops, best of 3: 632 ns per loop

La surcharge pour l'appel de fonction ci-dessus est d'environ 140ns .


En ligne if s sont imbriquées de la même manière, et peuvent se produire dans n'importe quelle position après le premier for :

data = [[1], [2, 3], [4, 5]]
output = [element for each_list in data
                if len(each_list) == 2
                for element in each_list
                if element != 5]
print(output)
# Out: [2, 3, 4]

Démo en direct

Par souci de lisibilité, cependant, vous devriez envisager d'utiliser des boucles traditionnelles. Cela est particulièrement vrai lorsque l’imbrication a plus de 2 niveaux de profondeur et / ou que la logique de la compréhension est trop complexe. la compréhension multiple de la liste de boucles imbriquées pourrait être source d'erreurs ou donner des résultats inattendus.

Filtre de refactoring et carte pour lister les compréhensions

Les fonctions de filter ou de map doivent souvent être remplacées par des listes compréhensibles . Guido Van Rossum le décrit bien dans une lettre ouverte en 2005 :

filter(P, S) est presque toujours écrit plus clair que [x for x in S if P(x)] , ce qui a l'énorme avantage que les usages les plus courants impliquent prédicats des comparaisons, par exemple x==42 , et définissant un lambda pour cela nécessite juste beaucoup plus d'effort pour le lecteur (plus le lambda est plus lent que la compréhension de la liste). Encore plus pour la map(F, S) qui devient [F(x) for x in S] . Bien sûr, dans de nombreux cas, vous pourriez utiliser des expressions de générateur à la place.

Les lignes de code suivantes sont considérées comme " non pythoniques " et provoquent des erreurs dans de nombreux linters en python.

filter(lambda x: x % 2 == 0, range(10)) # even numbers < 10
map(lambda x: 2*x, range(10)) # multiply each number by two
reduce(lambda x,y: x+y, range(10)) # sum of all elements in list

En prenant ce que nous avons appris de la citation précédente, nous pouvons décomposer ces expressions de filter et de map dans leurs compréhensions de liste équivalentes; aussi supprimer les fonctions lambda de chaque - rendant le code plus lisible dans le processus.

# Filter:
# P(x) = x % 2 == 0
# S = range(10)
[x for x in range(10) if x % 2 == 0]

# Map
# F(x) = 2*x
# S = range(10)
[2*x for x in range(10)]

La lisibilité devient encore plus évidente dans le cas des fonctions de chaînage. En raison de la lisibilité, les résultats d'une carte ou d'une fonction de filtre doivent être transmis à la suite; avec des cas simples, ceux-ci peuvent être remplacés par une seule compréhension de liste. De plus, nous pouvons facilement comprendre, à partir de la compréhension de la liste, le résultat de notre processus, où il y a plus de charge cognitive lors du raisonnement sur le processus Map & Filter enchaîné.

# Map & Filter
filtered = filter(lambda x: x % 2 == 0, range(10))
results = map(lambda x: 2*x, filtered)

# List comprehension
results = [2*x for x in range(10) if x % 2 == 0]

Refactoring - Référence rapide

  • Carte

    map(F, S) == [F(x) for x in S]
    
  • Filtre

    filter(P, S) == [x for x in S if P(x)]
    

F et P sont des fonctions qui transforment respectivement les valeurs d'entrée et renvoient un bool

Compréhension de liste imbriquée

Les compréhensions de liste imbriquées, contrairement aux compréhensions de liste avec des boucles imbriquées, sont des compréhensions de liste dans une compréhension de liste. L'expression initiale peut être n'importe quelle expression arbitraire, y compris une autre compréhension de la liste.

#List Comprehension with nested loop
[x + y for x in [1, 2, 3] for y in [3, 4, 5]]
#Out: [4, 5, 6, 5, 6, 7, 6, 7, 8]

#Nested List Comprehension
[[x + y for x in [1, 2, 3]] for y in [3, 4, 5]]
#Out: [[4, 5, 6], [5, 6, 7], [6, 7, 8]]

L'exemple imbriqué est équivalent à

l = []
for y in [3, 4, 5]:
    temp = []
    for x in [1, 2, 3]:
        temp.append(x + y)
    l.append(temp)

Un exemple où une compréhension imbriquée peut être utilisée pour transposer une matrice.

matrix = [[1,2,3],
          [4,5,6],
          [7,8,9]] 

[[row[i] for row in matrix] for i in range(len(matrix))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Comme for boucles imbriquées, il n'y a pas de limite à la façon dont les compréhensions profondes peuvent être imbriquées.

[[[i + j + k for k in 'cd'] for j in 'ab'] for i in '12']
# Out: [[['1ac', '1ad'], ['1bc', '1bd']], [['2ac', '2ad'], ['2bc', '2bd']]]

Itérer deux ou plusieurs listes simultanément dans la compréhension de liste

Pour itérer plus de deux listes simultanément dans la compréhension de la liste , on peut utiliser zip() comme:

>>> list_1 = [1, 2, 3 , 4]
>>> list_2 = ['a', 'b', 'c', 'd']
>>> list_3 = ['6', '7', '8', '9']

# Two lists
>>> [(i, j) for i, j in zip(list_1, list_2)]
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

# Three lists
>>> [(i, j, k) for i, j, k in zip(list_1, list_2, list_3)]
[(1, 'a', '6'), (2, 'b', '7'), (3, 'c', '8'), (4, 'd', '9')]

# so on ...


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow