Python Language
Liste des compréhensions
Recherche…
Introduction
Les compréhensions de liste en Python sont des constructions syntaxiques concises. Ils peuvent être utilisés pour générer des listes à partir d'autres listes en appliquant des fonctions à chaque élément de la liste. La section suivante explique et illustre l'utilisation de ces expressions.
Syntaxe
- [x + 1 pour x dans (1, 2, 3)] # compréhension de liste, donne [2, 3, 4]
- (x + 1 pour x dans (1, 2, 3)) # expression du générateur, donnera 2, puis 3, puis 4
- [x pour x dans (1, 2, 3) si x% 2 == 0] # liste compréhension avec filtre, donne [2]
- [x + 1 si x% 2 == 0 sinon x pour x dans (1, 2, 3)] # compréhension de liste avec ternaire
- [x + 1 si x% 2 == 0 sinon x pour x dans la plage (-3,4) si x> 0] # liste compréhension avec ternaire et filtrage
- {x pour x dans (1, 2, 2, 3)} # définir la compréhension, donne {1, 2, 3}
- {k: v pour k, v dans [('a', 1), ('b', 2)]} # dict la compréhension, donne {'a': 1, 'b': 2} (python 2.7+ et 3.0+ seulement)
- [x + y pour x dans [1, 2] pour y dans [10, 20]] # Boucles imbriquées, donne [11, 21, 12, 22]
- [x + y pour x dans [1, 2, 3] si x> 2 pour y dans [3, 4, 5]] # Condition vérifiée au 1er pour la boucle
- [x + y pour x dans [1, 2, 3] pour y dans [3, 4, 5] si x> 2] # Condition vérifiée en 2ème pour la boucle
- [x pour x dans xrange (10) si x% 2 == 0] # condition vérifiée si les nombres en boucle sont des nombres impairs
Remarques
Les compréhensions sont des constructions syntaxiques qui définissent des structures de données ou des expressions propres à un langage particulier. Une utilisation correcte des compréhensions réinterprète celles-ci en expressions faciles à comprendre. En tant qu'expressions, elles peuvent être utilisées:
- à droite des missions
- comme arguments pour les appels de fonction
- dans le corps d' une fonction lambda
- comme déclarations autonomes. (Par exemple:
[print(x) for x in range(10)]
)
Liste des compréhensions
Une compréhension de liste crée une nouvelle list
en appliquant une expression à chaque élément d’une itération . La forme la plus élémentaire est:
[ <expression> for <element> in <iterable> ]
Il y a aussi une condition optionnelle "if":
[ <expression> for <element> in <iterable> if <condition> ]
Chaque <element>
dans le <iterable>
est branché à la <expression>
si ( en option) <condition>
évalue à true . Tous les résultats sont renvoyés immédiatement dans la nouvelle liste. Les expressions de générateur sont évaluées paresseusement, mais les compréhensions de liste évaluent immédiatement tout l'itérateur - une mémoire consommatrice proportionnelle à la longueur de l'itérateur.
Pour créer une list
d'entiers carrés:
squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]
L'expression for
définit x
sur chaque valeur à partir de (1, 2, 3, 4)
. Le résultat de l'expression x * x
est ajouté à une list
interne. La list
interne est affectée aux squares
variables une fois remplie.
Outre une augmentation de la vitesse (comme expliqué ici ), une compréhension de la liste est à peu près équivalente à la suivante pour la boucle:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
L'expression appliquée à chaque élément peut être aussi complexe que nécessaire:
# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
autre
else
peut être utilisé dans les constructions de compréhension de liste, mais faites attention à la syntaxe. If / else clauses doivent être utilisés avant for
la boucle, et non après:
# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax
# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']
Notez que cela utilise une construction de langage différente, une expression conditionnelle , qui ne fait pas partie de la syntaxe de compréhension . Alors que if
après le for…in
fait partie des compréhensions de listes et permet de filtrer des éléments de la source itérable.
Double itération
L'ordre de la double itération [... for x in ... for y in ...]
est soit naturel, soit contre-intuitif. La règle de base est de suivre un équivalent for
boucle:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
Cela devient:
[str(x)
for i in range(3)
for x in foo(i)
]
Cela peut être compressé en une seule ligne comme [str(x) for i in range(3) for x in foo(i)]
Mutation en place et autres effets secondaires
Avant d'utiliser la compréhension de liste, comprenez la différence entre les fonctions appelées pour leurs effets secondaires (fonctions mutantes ou in- situ) qui renvoient généralement None
et les fonctions qui renvoient une valeur intéressante.
De nombreuses fonctions (en particulier les fonctions pures ) prennent simplement un objet et renvoient un objet. Une fonction sur place modifie l'objet existant, appelé effet secondaire . D'autres exemples incluent des opérations d'entrée et de sortie telles que l'impression.
list.sort()
trie une liste sur place (ce qui signifie qu'elle modifie la liste d'origine) et renvoie la valeur None
. Par conséquent, cela ne fonctionnera pas comme prévu dans une liste de compréhension:
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
Au lieu de cela, sorted()
renvoie une list
triée plutôt que de trier sur place:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]
L'utilisation de la compréhension des effets secondaires est possible, comme les fonctions d'E / S ou sur place. Pourtant, une boucle for est généralement plus lisible. Alors que cela fonctionne dans Python 3:
[print(x) for x in (1, 2, 3)]
Au lieu de cela, utilisez:
for x in (1, 2, 3):
print(x)
Dans certaines situations, les fonctions d'effets secondaires sont adaptés à la compréhension de la liste. random.randrange()
a pour effet secondaire de modifier l'état du générateur de nombres aléatoires, mais il renvoie également une valeur intéressante. En outre, next()
peut être appelé sur un itérateur.
Le générateur de valeur aléatoire suivant n'est pas pur, mais il a du sens car le générateur aléatoire est réinitialisé à chaque fois que l'expression est évaluée:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
Les espaces dans les listes compréhensibles
Des compréhensions de listes plus compliquées peuvent atteindre une durée indésirable ou devenir moins lisibles. Bien que moins courant dans les exemples, il est possible de diviser une compréhension de liste en plusieurs lignes comme suit:
[
x for x
in 'foo'
if x not in 'bar'
]
Compréhensions du dictionnaire
Une compréhension de dictionnaire est similaire à une compréhension de liste sauf qu'elle produit un objet dictionnaire au lieu d'une liste.
Un exemple de base:
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
ce qui est juste une autre façon d'écrire:
dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Comme avec une compréhension de liste, nous pouvons utiliser une instruction conditionnelle dans la compréhension du dict pour produire uniquement les éléments dict répondant à certains critères.
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
# Out: {'Exchange': 8, 'Overflow': 8}
Ou, réécrit en utilisant une expression de générateur.
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}
Commencer par un dictionnaire et utiliser la compréhension du dictionnaire comme filtre de paire clé-valeur
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}
Changement de clé et valeur du dictionnaire (dictionnaire inversé)
Si vous avez un dict contenant des valeurs hashable simples (les valeurs en double peuvent avoir des résultats inattendus):
my_dict = {1: 'a', 2: 'b', 3: 'c'}
et vous vouliez échanger les clés et les valeurs, vous pouvez prendre plusieurs approches en fonction de votre style de codage:
-
swapped = {v: k for k, v in my_dict.items()}
-
swapped = dict((v, k) for k, v in my_dict.iteritems())
-
swapped = dict(zip(my_dict.values(), my_dict))
-
swapped = dict(zip(my_dict.values(), my_dict.keys()))
-
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Si votre dictionnaire est volumineux, envisagez d' importer des outils itert et d'utiliser izip
ou imap
.
Fusion de dictionnaires
Combinez les dictionnaires et remplacez éventuellement les anciennes valeurs par une compréhension imbriquée du dictionnaire.
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Cependant, le déballage du dictionnaire ( PEP 448 ) peut être préféré.
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Remarque : les définitions de dictionnaires ont été ajoutées à Python 3.0 et transférées vers les versions 2.7+, contrairement aux interprétations de listes ajoutées en 2.0. Les versions <2.7 peuvent utiliser des expressions de générateur et le dict()
pour simuler le comportement des définitions de dictionnaires.
Expressions de générateur
Les expressions de générateur sont très similaires aux compréhensions de liste. La principale différence est que cela ne crée pas un ensemble complet de résultats à la fois; il crée un objet générateur qui peut ensuite être itéré.
Par exemple, voyez la différence dans le code suivant:
# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>
Ce sont deux objets très différents:
la compréhension de la liste renvoie un objet
list
alors que la compréhension du générateur renvoie ungenerator
.generator
objetsgenerator
ne peuvent pas être indexés et utilisent la fonctionnext
pour récupérer les éléments.
Note : Nous utilisons xrange
car il crée aussi un objet générateur. Si nous utilisions la plage, une liste serait créée. De plus, xrange
n'existe que dans la version ultérieure de python 2. Dans python 3, range
ne fait que renvoyer un générateur. Pour plus d'informations, reportez-vous à l' exemple des fonctions Différences entre plage et xrange .
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'
g.next() # 0
g.next() # 1
g.next() # 4
...
g.next() # 81
g.next() # Throws StopIteration Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
REMARQUE: La fonction
g.next()
doit être remplacée parnext(g)
etxrange
avecrange
carIterator.next()
etxrange()
n'existent pas dans Python 3.
Bien que les deux puissent être répétés de la même manière:
for i in [x**2 for x in range(10)]:
print(i)
"""
Out:
0
1
4
...
81
"""
for i in (x**2 for x in xrange(10)):
print(i)
"""
Out:
0
1
4
.
.
.
81
"""
Cas d'utilisation
Les expressions de générateur sont évaluées paresseusement, ce qui signifie qu'elles génèrent et renvoient chaque valeur uniquement lorsque le générateur est itéré. Ceci est souvent utile lorsque vous parcourez des jeux de données volumineux, évitant de créer un doublon du jeu de données en mémoire:
for square in (x**2 for x in range(1000000)):
#do something
Un autre cas d'utilisation courant consiste à éviter de procéder à une itération complète si cela n'est pas nécessaire. Dans cet exemple, un élément est extrait d'une API distante à chaque itération de get_objects()
. Des milliers d'objets peuvent exister, doivent être récupérés un par un et il suffit de savoir si un objet correspondant à un modèle existe. En utilisant une expression de générateur, lorsque nous rencontrons un objet correspondant au motif.
def get_objects():
"""Gets objects from an API one by one"""
while True:
yield get_next_item()
def object_matches_pattern(obj):
# perform potentially complex calculation
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Définir les compréhensions
La compréhension d'ensemble est similaire à la compréhension de liste et du dictionnaire , mais elle produit un ensemble , qui est une collection non ordonnée d'éléments uniques.
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}
# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}
# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
Gardez à l'esprit que les décors ne sont pas ordonnés. Cela signifie que l'ordre des résultats dans l'ensemble peut différer de celui présenté dans les exemples ci-dessus.
Remarque : La compréhension d'ensemble est disponible depuis python 2.7+, contrairement aux compréhensions de liste, qui ont été ajoutées en 2.0. Dans Python 2.2 à Python 2.6, la fonction set()
peut être utilisée avec une expression de générateur pour produire le même résultat:
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}
Eviter les opérations répétitives et coûteuses en utilisant une clause conditionnelle
Considérez la compréhension de la liste ci-dessous:
>>> def f(x):
... import time
... time.sleep(.1) # Simulate expensive function
... return x**2
>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]
Cela se traduit par deux appels à f(x)
pour 1 000 valeurs de x
: un appel pour générer la valeur et l'autre pour vérifier la condition if
. Si f(x)
est une opération particulièrement coûteuse, cela peut avoir des conséquences significatives sur les performances. Pire encore, si appeler f()
a des effets secondaires, cela peut avoir des résultats surprenants.
Au lieu de cela, vous devez évaluer l'opération coûteuse une seule fois pour chaque valeur de x
en générant une itération intermédiaire ( expression de générateur ) comme suit:
>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]
Ou, en utilisant l'équivalent de la carte intégrée:
>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]
Une autre façon d'obtenir un code plus lisible consiste à placer le résultat partiel ( v
dans l'exemple précédent) dans une itération (telle qu'une liste ou un tuple), puis à l'itérer. Puisque v
sera le seul élément de l’itérable, le résultat est que nous avons maintenant une référence à la sortie de notre fonction lente calculée une seule fois:
>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]
Cependant, dans la pratique, la logique du code peut être plus compliquée et il est important de la garder lisible. En général, une fonction de générateur séparée est recommandée par rapport à une ligne simple complexe:
>>> def process_prime_numbers(iterable):
... for x in iterable:
... if is_prime(x):
... yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]
Une autre façon d'éviter le calcul de f(x)
plusieurs fois est d'utiliser le @functools.lru_cache()
(Python 3.2+) décorateur de f(x)
. De cette façon, puisque la sortie de f
pour l'entrée x
a déjà été calculée une fois, l'invocation de la seconde fonction de la compréhension de la liste d'origine sera aussi rapide qu'une recherche dans un dictionnaire. Cette approche utilise la mémorisation pour améliorer l'efficacité, ce qui est comparable à l'utilisation des expressions génératrices.
Dites que vous devez aplatir une liste
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Certaines des méthodes pourraient être:
reduce(lambda x, y: x+y, l)
sum(l, [])
list(itertools.chain(*l))
Cependant, la compréhension de la liste fournirait la meilleure complexité temporelle.
[item for sublist in l for item in sublist]
Les raccourcis basés sur + (y compris l’utilisation implicite en somme) sont, par nécessité, O (L ^ 2) quand il ya L sous-listes - à mesure que la liste des résultats intermédiaires s'allonge, à chaque étape allouée, et tous les éléments du résultat intermédiaire précédent doivent être copiés (ainsi que quelques nouveaux ajoutés à la fin). Donc, pour des raisons de simplicité et sans perte de généralité, disons que vous avez L sous-listes de I articles chacun: les I premiers objets sont copiés en arrière L-1 fois, le second I éléments L-2 fois, et ainsi de suite; le nombre total de copies est I fois la somme de x pour x de 1 à L exclu, c'est-à-dire I * (L ** 2) / 2.
La compréhension de la liste génère une seule liste, une fois, et copie chaque élément (de son lieu de résidence d'origine à la liste de résultats) également une seule fois.
Compréhensions impliquant des tuples
La clause for
d'une compréhension de liste peut spécifier plus d'une variable:
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]
C'est comme for
boucles régulières:
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
# 3
# 7
# 11
Notez cependant que si l'expression qui commence la compréhension est un tuple, alors il faut la parenthèses:
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]
Compter les occurrences en utilisant la compréhension
Lorsque nous voulons compter le nombre d'éléments d'une itération, qui remplissent certaines conditions, nous pouvons utiliser la compréhension pour produire une syntaxe idiomatique:
# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
# Out: 95
Le concept de base peut être résumé comme suit:
- Itérer sur les éléments de la
range(1000)
. - Concaténer tous les nécessaires
if
les conditions. - Utilisez 1 comme expression pour renvoyer un 1 pour chaque élément répondant aux conditions.
- Résumez tous les
1
s pour déterminer le nombre d'éléments répondant aux conditions.
Note : Ici, nous ne collectons pas les 1
dans une liste (notez l’absence de crochets), mais nous les transmettons directement à la fonction sum
qui les additionne. Ceci est appelé une expression de générateur , qui est similaire à une compréhension.
Modification de types dans une liste
Les données quantitatives sont souvent lues comme des chaînes devant être converties en types numériques avant le traitement. Les types de tous les éléments de la liste peuvent être convertis avec une fonction List Comprehension ou la fonction map()
.
# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]
# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0]