Simplifier le bouclage avec des compteurs – Real Python

By | novembre 18, 2020

Formation Python

En Python, un pour loop est généralement écrite comme une boucle sur un objet itérable. Cela signifie que vous n'avez pas besoin d'une variable de comptage pour accéder aux éléments de l'itérable. Parfois, cependant, vous voulez avoir une variable qui change à chaque itération de boucle. Plutôt que de créer et d’incrémenter une variable vous-même, vous pouvez utiliser Python énumérer() pour obtenir un compteur et la valeur de l'itérable en même temps!

Dans ce didacticiel, vous découvrirez comment:

  • Utilisation énumérer() pour obtenir un compteur dans une boucle
  • Appliquer énumérer() à afficher le nombre d'articles
  • Utilisation énumérer() avec expressions conditionnelles
  • Mettez en œuvre le vôtre fonction équivalente à énumérer()
  • Décompresser les valeurs retourné par énumérer()

Commençons!

Itérer avec pour Boucles en Python

UNE pour boucle en Python utilise itération basée sur la collection. Cela signifie que Python affecte l'élément suivant d'un itérable à la variable de boucle à chaque itération, comme dans cet exemple:

>>>

>>> valeurs = [[[["une", "b", "c"]

>>> pour valeur dans valeurs:
...     impression(valeur)
...
une
b
c

Dans cet exemple, valeurs est une liste à trois chaînes, "une", "b", et "c". En Python, les listes sont un type d'objet itérable. dans le pour loop, la variable de boucle est valeur. A chaque itération de la boucle, valeur est défini sur l'élément suivant de valeurs.

Ensuite, vous imprimez valeur sur l'écran. L'avantage de l'itération basée sur la collection est qu'elle permet d'éviter l'erreur hors-un-par-un qui est courante dans d'autres langages de programmation.

Imaginez maintenant qu'en plus de la valeur elle-même, vous souhaitiez imprimer l'index de l'élément de la liste à l'écran à chaque itération. Une façon d'aborder cette tâche consiste à créer une variable pour stocker l'index et le mettre à jour à chaque itération:

>>>

>>> indice = 0

>>> pour valeur dans valeurs:
...     impression(indice, valeur)
...     indice + = 1
...
0 a
1 b
2 c

Dans cet exemple, indice est un entier qui garde une trace de votre position dans la liste. A chaque itération de la boucle, vous imprimez indice aussi bien que valeur. La dernière étape de la boucle consiste à mettre à jour le numéro stocké dans indice par un. Un bug courant se produit lorsque vous oubliez de mettre à jour indice à chaque itération:

>>>

>>> indice = 0

>>> pour valeur dans valeurs:
...     impression(indice, valeur)
...
0 a
0 b
0 c

Dans cet exemple, indice reste à 0 à chaque itération car il n'y a pas de code pour mettre à jour sa valeur à la fin de la boucle. En particulier pour les boucles longues ou compliquées, ce type de bogue est notoirement difficile à localiser.

Une autre façon courante d'aborder ce problème consiste à utiliser intervalle() combiné avec len () pour créer automatiquement un index. De cette façon, vous n'avez pas besoin de vous rappeler de mettre à jour l'index:

>>>

>>> pour indice dans intervalle(len(valeurs)):
...     valeur = valeurs[[[[indice]
...     impression(indice, valeur)
...
0 a
1 b
2 c

Dans cet exemple, len (valeurs) renvoie la longueur de valeurs, lequel est 3. ensuite intervalle() crée un itérateur à partir de la valeur de départ par défaut de 0 jusqu'à ce qu'il atteigne len (valeurs) moins un. Dans ce cas, indice devient votre variable de boucle. Dans la boucle, vous définissez valeur égal à l'article dans valeurs à la valeur actuelle de indice. Enfin, vous imprimez indice et valeur.

Avec cet exemple, un bogue courant qui peut survenir est lorsque vous oubliez de mettre à jour valeur au début de chaque itération. Ceci est similaire au bogue précédent consistant à oublier de mettre à jour l'index. C’est l’une des raisons pour lesquelles cette boucle n’est pas considérée comme pythonique.

Cet exemple est également quelque peu restreint car valeurs doit autoriser l'accès à ses éléments à l'aide d'indices entiers. Les itérables qui permettent ce type d'accès sont appelés séquences en Python.

Heureusement, Python énumérer() vous permet d'éviter tous ces problèmes. C'est un intégré , ce qui signifie qu'il est disponible dans toutes les versions de Python depuis son ajout dans Python 2.3, en 2003.

Utilisation de Python énumérer()

Vous pouvez utiliser énumérer() dans une boucle de la même manière que vous utilisez l'objet itérable d'origine. Au lieu de mettre l'itérable directement après dans dans le pour loop, vous le mettez entre parenthèses de énumérer(). Vous devez également modifier un peu la variable de boucle, comme indiqué dans cet exemple:

>>>

>>> pour compter, valeur dans énumérer(valeurs):
...     impression(compter, valeur)
...
0 a
1 b
2 c

Lorsque vous utilisez énumérer(), la fonction vous rend deux variables de boucle:

  1. le compter de l'itération actuelle
  2. le valeur de l'élément à l'itération courante

Tout comme avec un normal pour loop, les variables de boucle peuvent être nommées comme vous voulez qu'elles soient nommées. Tu utilises compter et valeur dans cet exemple, mais ils pourraient être nommés je et v ou tout autre nom Python valide.

Avec énumérer(), vous n’avez pas besoin de vous rappeler d’accéder à l’élément à partir de l’itérable, et vous n’avez pas besoin de vous rappeler d’avancer l’index à la fin de la boucle. Tout est automatiquement géré pour vous par la magie de Python!

Python énumérer() a un argument supplémentaire que vous pouvez utiliser pour contrôler la valeur de départ du décompte. Par défaut, la valeur de départ est 0 car les types de séquence Python sont indexés en commençant par zéro. En d'autres termes, lorsque vous souhaitez récupérer le premier élément d'une liste, vous utilisez index 0:

>>>

>>> impression(valeurs[[[[0])
une

Vous pouvez voir dans cet exemple que l'accès valeurs avec l'index 0 donne le premier élément, une. Cependant, il arrive souvent que vous ne vouliez pas que le décompte énumérer() commencer à 0. Par exemple, vous souhaiterez peut-être imprimer un nombre de comptage naturel en tant que sortie pour l'utilisateur. Dans ce cas, vous pouvez utiliser le début argument pour énumérer() pour changer le compte de départ:

>>>

>>> pour compter, valeur dans énumérer(valeurs, début=1):
...     impression(compter, valeur)
...
1 a
2 b
3 c

Dans cet exemple, vous passez début = 1, qui commence compter avec la valeur 1 sur la première itération de boucle. Comparez cela avec les exemples précédents, dans lesquels début avait la valeur par défaut de 0et voyez si vous pouvez repérer la différence.

Pratiquer avec Python énumérer()

Tu devrais utiliser énumérer() chaque fois que vous avez besoin d'utiliser le nombre et un élément dans une boucle. Garde en tête que énumérer() incrémente le nombre de un à chaque itération. Cependant, cela ne limite que légèrement votre flexibilité. Étant donné que le nombre est un entier Python standard, vous pouvez l'utiliser de plusieurs manières. Dans les prochaines sections, vous verrez quelques utilisations de énumérer().

Nombre naturel d'éléments itérables

Dans la section précédente, vous avez vu comment utiliser énumérer() avec début pour créer un nombre de comptage naturel à imprimer pour un utilisateur. énumérer() est également utilisé comme ceci dans la base de code Python. Vous pouvez voir un exemple dans un script qui lit les fichiers reST et informe l'utilisateur en cas de problèmes de formatage.

Cet exemple est légèrement modifié de rstlint.py. Ne vous inquiétez pas trop de la façon dont cette fonction détecte les problèmes. Le but est de montrer une utilisation réelle de énumérer():

    1def check_whitespace(lignes):
    2    "" "Vérifiez les problèmes d'espaces blancs et de longueur de ligne." ""
    3    pour non, ligne dans énumérer(lignes):
    4        si " r" dans ligne:
    5            rendement non+1, "\r en ligne "
    6        si " t" dans ligne:
    sept            rendement non+1, "OMG TABS !!! 1"
    8        si ligne[:[:[:[:-1].bande("  t") ! = ligne[:[:[:[:-1]:
    9            rendement non+1, "espace blanc de fin"

check_whitespace () prend un argument, lignes, qui sont les lignes du fichier qui doivent être évaluées. Sur la troisième ligne de check_whitespace (), énumérer() est utilisé dans une boucle sur lignes. Cela renvoie le numéro de ligne, abrégé en non, et le ligne. Depuis début n'est pas utilisé, non est un compteur de base zéro des lignes du fichier. check_whitespace () effectue ensuite plusieurs vérifications pour les caractères déplacés:

  1. Le retour chariot ( r)
  2. Le caractère de tabulation ( t)
  3. Tous les espaces ou tabulations à la fin de la ligne

Lorsqu'un de ces éléments est présent, check_whitespace () donne le numéro de ligne actuel et un message utile pour l'utilisateur. La variable count non a 1 ajouté à celui-ci afin qu'il renvoie le numéro de ligne de comptage plutôt qu'un index de base zéro. Lorsqu'un utilisateur de rstlint.py lit le message, ils sauront à quelle ligne aller et quoi réparer.

Instructions conditionnelles pour ignorer les éléments

L'utilisation d'instructions conditionnelles pour traiter des éléments peut être une technique très puissante. Parfois, vous devrez peut-être effectuer une action uniquement sur la toute première itération d'une boucle, comme dans cet exemple:

>>>

>>> utilisateurs = [[[["Utilisateur de test", "Utilisateur réel 1", "Utilisateur réel 2"]
>>> pour indice, utilisateur dans énumérer(utilisateurs):
...     si indice == 0:
...         impression("Sortie extra verbeuse pour:", utilisateur)
...     impression(utilisateur)
...
Sortie très détaillée pour: utilisateur de test
Utilisateur réel 1
Utilisateur réel 2

Dans cet exemple, vous utilisez une liste comme base de données fictive d'utilisateurs. Le premier utilisateur est votre utilisateur de test, vous souhaitez donc imprimer des informations de diagnostic supplémentaires sur cet utilisateur. Étant donné que vous avez configuré votre système pour que l’utilisateur du test soit le premier, vous pouvez utiliser la première valeur d’index de la boucle pour imprimer une sortie plus détaillée.

Vous pouvez également combiner des opérations mathématiques avec des conditions pour le nombre ou l'index. Par exemple, vous devrez peut-être renvoyer des éléments à partir d'un itérable, mais uniquement s'ils ont un index pair. Vous pouvez le faire en utilisant énumérer():

>>>

>>> def even_items(itérable):
...     "" "Renvoie les éléments de` `iterable`` lorsque leur index est pair." ""
...     valeurs = []
...     pour indice, valeur dans énumérer(itérable, début=1):
...         si ne pas indice % 2:
...             valeurs.ajouter(valeur)
...     revenir valeurs
...

even_items () prend un argument, appelé itérable, cela devrait être un type d'objet sur lequel Python peut boucler. Première, valeurs est initialisé pour être une liste vide. Ensuite, vous créez un pour boucler itérable avec énumérer() Et mettre début = 1.

Dans le pour boucle, vous vérifiez si le reste de la division indice par 2 est zéro. Si tel est le cas, vous ajoutez l'élément à valeurs. Enfin, vous revenez valeurs.

Vous pouvez rendre le code plus pythonique en utilisant une compréhension de liste pour faire la même chose sur une ligne sans initialiser la liste vide:

>>>

>>> def even_items(itérable):
...     revenir [[[[v pour je, v dans énumérer(itérable, début=1) si ne pas je % 2]
...

Dans cet exemple de code, even_items () utilise une compréhension de liste plutôt qu'un pour boucle pour extraire chaque élément de la liste dont l'index est un nombre pair.

Vous pouvez vérifier que even_items () fonctionne comme prévu en récupérant les éléments même indexés à partir d'une plage d'entiers de 1 à dix. Le résultat sera [2, 4, 6, 8, 10]:

>>>

>>> seq = liste(intervalle(1, 11))

>>> impression(seq)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> even_items(seq)
[2, 4, 6, 8, 10]

Comme prévu, even_items () renvoie les éléments à index pair de seq. Ce n’est pas le moyen le plus efficace d’obtenir les nombres pairs lorsque vous travaillez avec des entiers. Cependant, maintenant que vous avez vérifié que even_items () fonctionne correctement, vous pouvez obtenir les lettres paires de l'alphabet ASCII:

>>>

>>> alphabet = "abcdefghijklmnopqrstuvwxyz"

>>> even_items(alphabet)
['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z']

alphabet est une chaîne contenant les vingt-six lettres minuscules de l'alphabet ASCII. Appel even_items () et en passant alphabet renvoie une liste de lettres alternées de l'alphabet.

Les chaînes Python sont des séquences, qui peuvent être utilisées dans des boucles ainsi que dans l'indexation et le découpage d'entiers. Ainsi, dans le cas des chaînes, vous pouvez utiliser des crochets pour obtenir les mêmes fonctionnalités que even_items () plus efficacement:

>>>

>>> liste(alphabet[[[[1::2])
['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z']

En utilisant le découpage de chaînes ici, vous donnez l'index de départ 1, qui correspond au deuxième élément. Il n'y a pas d'index de fin après le premier deux-points, donc Python va à la fin de la chaîne. Ensuite, vous ajoutez le deuxième deux-points suivi d'un 2 pour que Python prenne tous les autres éléments.

Cependant, comme vous l'avez vu précédemment, les générateurs et les itérateurs ne peuvent pas être indexés ou découpés. Vous trouverez donc toujours énumérer() utile. Pour continuer l'exemple précédent, vous pouvez créer une fonction de générateur qui produit des lettres de l'alphabet à la demande:

>>>

>>> def alphabet():
...     alpha = "abcdefghijklmnopqrstuvwxyz"
...     pour une dans alpha:
...         rendement une

>>> alphabet[[[[1::2]
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
Erreur-type: L'objet 'function' n'est pas en indice

>>> even_items(alphabet())
['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z']

Dans cet exemple, vous définissez alphabet(), une fonction génératrice qui produit les lettres de l'alphabet une par une lorsque la fonction est utilisée dans une boucle. Les fonctions Python, qu'il s'agisse de générateurs ou de fonctions normales, ne sont pas accessibles par l'indexation entre crochets. Vous essayez ceci sur la deuxième ligne, et cela soulève un Erreur-type.

Vous pouvez cependant utiliser des fonctions de générateur dans des boucles, et vous le faites sur la dernière ligne en passant alphabet() à even_items (). Vous pouvez voir que le résultat est le même que les deux exemples précédents.

Comprendre Python énumérer()

Dans les dernières sections, vous avez vu des exemples de quand et comment utiliser énumérer() à votre avantage. Maintenant que vous maîtrisez les aspects pratiques de énumérer(), vous pouvez en savoir plus sur le fonctionnement de la fonction en interne.

Pour mieux comprendre comment énumérer() fonctionne, vous pouvez implémenter votre propre version avec Python. Votre version de énumérer() a deux exigences. Cela devrait:

  1. Acceptez un itérable et une valeur de comptage de départ comme arguments
  2. Renvoyer un tuple avec la valeur de comptage actuelle et l'élément associé de l'itérable

Une façon d'écrire une fonction répondant à ces spécifications est donnée dans la documentation Python:

>>>

>>> def mon_enumerate(séquence, début=0):
...     n = début
...     pour elem dans séquence:
...         rendement n, elem
...         n + = 1
...

mon_enumerate () prend deux arguments, séquence et début. La valeur par défaut de début est 0. Dans la définition de fonction, vous initialisez n être la valeur de début et exécutez un pour boucle sur le séquence.

Pour chaque elem dans séquence, toi rendement contrôlez vers le lieu appelant et renvoyez les valeurs actuelles de n et elem. Enfin, vous incrémentez n pour se préparer pour la prochaine itération. Tu peux voir mon_enumerate () en action ici:

>>>

>>> saisons = [[[["Printemps", "Été", "Tomber", "Hiver"]

>>> mon_enumerate(saisons)


>>> liste(mon_enumerate(saisons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

>>> liste(mon_enumerate(saisons, début=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

Tout d'abord, vous créez une liste des quatre saisons avec lesquelles travailler. Ensuite, vous montrez cet appel mon_enumerate () avec saisons comme le séquence crée un objet générateur. C'est parce que vous utilisez le rendement mot-clé pour renvoyer les valeurs à l'appelant.

Enfin, vous créez deux listes à partir de mon_enumerate (), celui dans lequel la valeur de départ est laissée par défaut, 0, et un dans lequel début est changé en 1. Dans les deux cas, vous vous retrouvez avec une liste de tuples dans laquelle le premier élément de chaque tuple est le nombre et le deuxième élément est la valeur de saisons.

Bien que vous puissiez implémenter une fonction équivalente pour énumérer() en seulement quelques lignes de code Python, le code réel pour énumérer() est écrit en C. Cela signifie qu'il est super rapide et efficace.

Déballage d'arguments avec énumérer()

Lorsque vous utilisez énumérer() dans un pour boucle, vous dites à Python d'utiliser deux variables, une pour le nombre et une pour la valeur elle-même. Vous pouvez le faire en utilisant un concept Python appelé déballage des arguments.

Le déballage des arguments est l'idée qu'un tuple peut être divisé en plusieurs variables en fonction de la longueur de la séquence. Par exemple, vous pouvez décompresser un tuple de deux éléments en deux variables:

>>>

>>> tuple_2 = (dix, "une")
>>> first_elem, second_elem = tuple_2
>>> first_elem
dix
>>> second_elem
'une'

Tout d'abord, vous créez un tuple avec deux éléments, dix et "une". Ensuite, vous décompressez ce tuple dans first_elem et second_elem, qui reçoivent chacune une des valeurs du tuple.

Quand vous appelez énumérer() et passez une séquence de valeurs, Python renvoie un itérateur. Lorsque vous demandez à l'itérateur sa valeur suivante, il produit un tuple avec deux éléments. Le premier élément du tuple est le nombre et le deuxième élément est la valeur de la séquence que vous avez passée:

>>>

>>> valeurs = [[[["une", "b"]
>>> enum_instance = énumérer(valeurs)
>>> enum_instance

>>> prochain(enum_instance)
(0, 'a')
>>> prochain(enum_instance)
(1, 'b')
>>> prochain(enum_instance)
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
StopIteration

Dans cet exemple, vous créez une liste appelée valeurs avec deux éléments, "une" et "b". Puis tu passes valeurs à énumérer() et attribuez la valeur de retour à enum_instance. Lorsque vous imprimez enum_instance, vous pouvez voir qu'il s'agit d'une instance de énumérer() avec une adresse mémoire particulière.

Ensuite, vous utilisez la fonction intégrée de Python prochain() pour obtenir la valeur suivante de enum_instance. La première valeur qui enum_instance renvoie est un tuple avec le nombre 0 et le premier élément de valeurs, lequel est "une".

Appel prochain() encore une fois enum_instance donne un autre tuple, cette fois avec le nombre 1 et le deuxième élément de valeurs, "b". Enfin, appeler prochain() une fois de plus augmente StopIteration car il n'y a plus de valeurs à renvoyer enum_instance.

Lorsqu'un itérable est utilisé dans un pour boucle, Python appelle automatiquement prochain() au début de chaque itération jusqu'à StopIteration est soulevé. Python affecte la valeur qu'il récupère de l'itérable à la variable de boucle.

Si un itérable renvoie un tuple, vous pouvez utiliser la décompression d'arguments pour affecter les éléments du tuple à plusieurs variables. C'est ce que vous avez fait plus tôt dans ce didacticiel en utilisant deux variables de boucle.

Une autre fois, vous avez peut-être vu une dispute déballer avec un pour la boucle est avec le intégré Zip *: français(), ce qui vous permet de parcourir deux séquences ou plus en même temps. À chaque itération, Zip *: français() renvoie un tuple qui recueille les éléments de toutes les séquences qui ont été passées:

>>>

>>> première = [[[["une", "b", "c"]
>>> seconde = [[[["ré", "e", "F"]
>>> troisième = [[[["g", "h", "je"]
>>> pour un, deux, Trois dans Zip *: français(première, seconde, troisième):
...     impression(un, deux, Trois)
...
un d g
b e h
c f i

En utilisant Zip *: français(), vous pouvez parcourir première, seconde, et troisième en même temps. dans le pour boucle, vous affectez l'élément de première à un, de seconde à deux, et de troisième à Trois. Ensuite, vous imprimez les trois valeurs.

Vous pouvez combiner Zip *: français() et énumérer() en utilisant imbriqué déballage des arguments:

>>>

>>> pour compter, (un, deux, Trois) dans énumérer(Zip *: français(première, seconde, troisième)):
...     impression(compter, un, deux, Trois)
...
0 un d g
1 b e h
2 c f i

dans le pour boucle dans cet exemple, vous imbriquez Zip *: français() à l'intérieur énumérer(). Cela signifie qu'à chaque fois pour boucle itère, énumérer() renvoie un tuple avec la première valeur comme compte et la seconde valeur comme un autre tuple contenant les éléments des arguments à Zip *: français(). Pour décompresser la structure imbriquée, vous devez ajouter des parenthèses pour capturer les éléments du tuple d'éléments imbriqués de Zip *: français().

Il existe d'autres moyens d'imiter le comportement de énumérer() combiné avec Zip *: français(). Une méthode utilise itertools.count (), qui renvoie des entiers consécutifs par défaut, en commençant à zéro. Vous pouvez modifier l'exemple précédent pour utiliser itertools.count ():

>>>

>>> importer itertools
>>> pour compter, un, deux, Trois dans Zip *: français(itertools.compter(), première, seconde, troisième):
...     impression(compter, un, deux, Trois)
...
0 un d g
1 b e h
2 c f i

En utilisant itertools.count () dans cet exemple vous permet d'utiliser un seul Zip *: français() appelez pour générer le décompte ainsi que les variables de boucle sans déballage d'arguments imbriqués.

Conclusion

Python énumérer() vous permet d'écrire Pythonic pour boucles lorsque vous avez besoin d'un nombre et de la valeur d'un itérable. Le grand avantage de énumérer() est qu'il renvoie un tuple avec le compteur et la valeur, vous n'avez donc pas à incrémenter le compteur vous-même. Il vous donne également la possibilité de modifier la valeur de départ du compteur.

Dans ce didacticiel, vous avez appris à:

  • Utilisez Python énumérer() dans votre pour boucles
  • Appliquer énumérer() dans quelques exemples du monde réel
  • Obtenez des valeurs de énumérer() en utilisant déballage des arguments
  • Mettez en œuvre le vôtre fonction équivalente à énumérer()

Vous avez aussi vu énumérer() utilisé dans du code réel, y compris dans le référentiel de code CPython. Vous avez maintenant le super pouvoir de simplifier vos boucles et de rendre votre code Python élégant!