Supposons que j'ai une liste d'éléments et que je veuille en parcourir les premiers éléments:
items = list(range(10)) # I mean this to represent any kind of iterable.
limit = 5
Le naïf Python provenant d’autres langues) écrirait probablement ce code parfaitement utilisable et performant (s’il n’est pas idiotomatique):
index = 0
for item in items: # Python's `for` loop is a for-each.
print(item) # or whatever function of that item.
index += 1
if index == limit:
break
Mais Python a enumerate, ce qui absorbe gentiment environ la moitié de ce code:
for index, item in enumerate(items):
print(item)
if index == limit: # There's gotta be a better way.
break
Nous avons donc coupé le code supplémentaire de moitié. Mais il doit y avoir un meilleur moyen.
Si enumerate a pris un autre argument optionnel stop
(par exemple, il faut un argument start
comme celui-ci: enumerate(items, start=1)
) qui serait, je pense, idéal, mais ce qui suit ne n'existe pas (voir le documentation sur énumérer ici ):
# hypothetical code, not implemented:
for _, item in enumerate(items, start=0, stop=limit): # `stop` not implemented
print(item)
Notez qu'il ne serait pas nécessaire de nommer le index
car il n'est pas nécessaire de le référencer.
Existe-t-il un moyen idiomatique d’écrire ce qui précède? Comment?
Une question secondaire: pourquoi cela n’est-il pas intégré à énumérer?
Comment puis-je limiter les itérations d'une boucle en Python?
_for index, item in enumerate(items): print(item) if index == limit: break
_Existe-t-il une méthode idiomatique plus courte pour écrire ce qui précède? Comment?
Zip
s'arrête sur le plus court itérable de ses arguments. (Contrairement au comportement de _Zip_longest
_, qui utilise l'itéré le plus long.)
range
peut fournir un nombre itérable limité que nous pouvons transmettre à Zip avec notre fichier itérable primaire.
Nous pouvons donc passer un objet range
(avec son argument stop
) à Zip
et l'utiliser comme une énumération limitée.
Zip(range(limit), items)
Utiliser Python 3, Zip
et range
renvoient des itérables, qui acheminent les données au lieu de les matérialiser dans des listes pour les étapes intermédiaires.
_for index, item in Zip(range(limit), items):
print(index, item)
_
Pour obtenir le même comportement dans Python 2, il suffit de remplacer xrange
par range
et _itertools.izip
_ par Zip
.
_from itertools import izip
for index, item in izip(xrange(limit), items):
print(item)
_
itertools.islice
_Vous pouvez utiliser _itertools.islice
_:
_for item in itertools.islice(items, 0, stop):
print(item)
_
qui ne nécessite pas d'affectation à l'index.
enumerate(islice(items, stop))
pour obtenir l'indexComme le souligne Pablo Ruiz Ruiz, nous pouvons également composer islice avec énumération.
_for index, item in enumerate(islice(items, limit)):
print(index, item)
_
Pourquoi n'est-ce pas intégré à
enumerate
?
Voici l'énumération implémentée dans pure Python (avec les modifications possibles pour obtenir le comportement souhaité dans les commentaires)):
_def enumerate(collection, start=0): # could add stop=None
i = start
it = iter(collection)
while 1: # could modify to `while i != stop:`
yield (i, next(it))
i += 1
_
Ce qui précède serait moins performant pour ceux qui utilisent déjà énumérer, car il faudrait vérifier s’il est temps d’arrêter chaque itération. Nous pouvons simplement vérifier et utiliser l'ancienne énumération si vous n'obtenez pas d'argument stop:
__enumerate = enumerate
def enumerate(collection, start=0, stop=None):
if stop is not None:
return Zip(range(start, stop), collection)
return _enumerate(collection, start)
_
Cette vérification supplémentaire aurait un impact légèrement négligeable sur les performances.
Quant à pourquoi enumerate n'a pas d'argument stop, cela avait été proposé à l'origine (voir PEP 279 ):
Cette fonction a été initialement proposée avec des arguments optionnels de démarrage et d'arrêt. GvR [Guido van Rossum] a fait remarquer que l'appel de fonction
enumerate(seqn, 4, 6)
avait une interprétation alternative plausible comme une tranche qui renverrait les quatrième et cinquième éléments de la séquence. Pour éviter toute ambiguïté, les arguments optionnels ont été supprimés même si cela impliquait une perte de flexibilité en tant que compteur de boucle. Cette flexibilité était la plus importante pour le cas courant de compter à partir d'un, comme dans:_for linenum, line in enumerate(source,1): print linenum, line
_
Donc, apparemment start
a été conservé car il était très précieux et stop
a été abandonné car il comportait moins de cas d'utilisation et contribuait à la confusion quant à l'utilisation de la nouvelle fonction.
Une autre réponse dit:
Pourquoi ne pas simplement utiliser
_for item in items[:limit]: # or limit+1, depends
_
Voici quelques inconvénients:
Vous ne devriez utiliser le découpage en tranches qu'avec une notation en indice lorsque vous en comprenez les limites et que vous réalisez une copie ou une vue.
Je présume que la communauté Python) connaît maintenant l’utilisation d’énumérer, les coûts de confusion seraient compensés par la valeur de l’argument.
Jusque-là, vous pouvez utiliser:
_for index, element in Zip(range(limit), items):
...
_
ou
_for index, item in enumerate(islice(items, limit)):
...
_
ou, si vous n'avez pas du tout besoin de l'index:
_for element in islice(items, 0, limit):
...
_
Et évitez de découper avec la notation en indice, sauf si vous en comprenez les limites.
Vous pouvez utiliser itertools.islice
pour cela. Il accepte les arguments start
, stop
et step
. Si vous ne transmettez qu'un seul argument, il est considéré comme stop
. Et ça marchera avec n'importe quel itérable.
itertools.islice(iterable, stop)
itertools.islice(iterable, start, stop[, step])
Démo:
>>> from itertools import islice
>>> items = list(range(10))
>>> limit = 5
>>> for item in islice(items, limit):
print item,
...
0 1 2 3 4
Exemple de docs:
islice('ABCDEFG', 2) --> A B
islice('ABCDEFG', 2, 4) --> C D
islice('ABCDEFG', 2, None) --> C D E F G
islice('ABCDEFG', 0, None, 2) --> A C E G
Pourquoi ne pas simplement utiliser
for item in items[:limit]: # or limit+1, depends
print(item) # or whatever function of that item.
Cela ne fonctionnera que pour certains iterables, mais puisque vous avez spécifié des listes, cela fonctionne.
Cela ne fonctionne pas si vous utilisez des sets ou des dicts, etc.
Pourquoi ne pas boucler jusqu'à la limite ou la fin de la liste, selon la première éventualité, comme ceci:
items = range(10)
limit = 5
for i in range(min(limit, len(items))):
print items[i]
Sortie:
0
1
2
3
4
Passer islice avec la limite intérieure énumérée
a = [2,3,4,2,1,4]
for a, v in enumerate(islice(a, 3)):
print(a, v)
Sortie:
0 2
1 3
2 4