Python Language
Liste des compréhensions
Recherche…
Introduction
[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]
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]
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]
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]
À 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]
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 exemplex==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 lamap(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)]
où 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 ...