façon pythonique de faire quelque chose N fois sans variable d'index?


161

Chaque jour, j'aime de plus en plus python.

Aujourd'hui, j'écrivais du code comme:

for i in xrange(N):
    do_something()

J'ai dû faire quelque chose N fois. Mais chaque fois ne dépendait pas de la valeur de i(variable d'index). J'ai réalisé que je créais une variable que je n'avais jamais utilisée ( i), et j'ai pensé "Il y a sûrement une manière plus pythonique de faire cela sans avoir besoin de cette variable d'index inutile."

Alors ... la question est: savez-vous comment faire cette tâche simple d'une manière plus (pythonique) belle?


7
Je viens d'apprendre la variable _, mais sinon, je considérerais la façon dont vous le faites comme pythonique. Je ne pense pas avoir jamais vu une simple boucle for réalisée autrement, du moins en python. Bien que je sois sûr qu'il y a des cas d'utilisation spécifiques où vous le regardez et dites "Attendez, ça a l'air terrible" - mais en général, xrange est le moyen préféré (pour autant que je sache).
Wayne Werner le


5
REMARQUE: xrange n'existe pas dans Python3. Utilisez rangeplutôt.
John Henckel

Réponses:


110

Une approche légèrement plus rapide que le bouclage xrange(N)est:

import itertools

for _ in itertools.repeat(None, N):
    do_something()

3
Combien plus vite? Y a-t-il encore une différence dans Python 3.1?
Hamish Grubijan

15
@Hamish: Mon test avec 2.6 dit 32% plus rapide (23.2 us vs 17.6 us pour N = 1000). Mais c'est vraiment un moment de toute façon. Je choisirais par défaut le code de l'OP car il est plus immédiatement lisible (pour moi).
Mike Boers

3
C'est bon de connaître la vitesse. Je fais certainement écho au sentiment de Mike que le code de l'OP soit plus lisible.
Wayne Werner le

@Wayne, je suppose que l'habitude est vraiment très puissante - à part le fait que vous y êtes habitué, pourquoi autrement "compterait-il de 0 à N-1 [[et ignorerait complètement le décompte]] chaque fois que vous effectuez ce décompte -opération indépendante "être intrinsèquement plus claire que" répéter N fois l'opération suivante "...?
Alex Martelli le

4
êtes-vous sûr que la vitesse est vraiment pertinente? N'est-il pas vrai que si vous faites quelque chose d'important dans cette boucle, cela prendra très probablement des centaines ou des milliers de temps autant que le style d'itération que vous avez choisi?
Henning

55

Utilisez la variable _, comme je l'ai appris lorsque j'ai posé cette question , par exemple:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in xrange(power):
    product *= num
print product

6
Ce n'est pas le downvoter, mais c'est peut-être parce que vous faites référence à un autre article au lieu d'ajouter plus de détails dans la réponse
Downgoat

5
@Downgoat: Merci pour vos commentaires. Cela dit, il n'y a pas grand chose à dire sur cet idiome. Mon point en faisant référence à un autre article était de souligner qu'une recherche aurait pu produire la réponse. Je trouve ironique que cette question ait plusieurs fois les votes positifs que l'autre.
GreenMatt


10

puisque la fonction est un citoyen de première classe, vous pouvez écrire un petit wrapper (à partir des réponses d'Alex)

def repeat(f, N):
    for _ in itertools.repeat(None, N): f()

alors vous pouvez passer la fonction comme argument.


@Hamish: Presque rien. (17,8 us par boucle dans les mêmes conditions que les timings pour la réponse d'Alex, pour une différence de 0,2 us).
Mike Boers

9

Le _ est la même chose que x. Cependant, c'est un idiome python utilisé pour indiquer un identifiant que vous n'avez pas l'intention d'utiliser. En python, ces identificateurs ne prennent pas de mémoire ni n'allouent d'espace comme le font les variables dans d'autres langages. C'est facile de l'oublier. Ce ne sont que des noms qui pointent vers des objets, dans ce cas un entier à chaque itération.


8

J'ai trouvé les différentes réponses très élégantes (en particulier celles d'Alex Martelli) mais je voulais quantifier les performances de première main, alors j'ai concocté le scénario suivant:

from itertools import repeat
N = 10000000

def payload(a):
    pass

def standard(N):
    for x in range(N):
        payload(None)

def underscore(N):
    for _ in range(N):
        payload(None)

def loopiter(N):
    for _ in repeat(None, N):
        payload(None)

def loopiter2(N):
    for _ in map(payload, repeat(None, N)):
        pass

if __name__ == '__main__':
    import timeit
    print("standard: ",timeit.timeit("standard({})".format(N),
        setup="from __main__ import standard", number=1))
    print("underscore: ",timeit.timeit("underscore({})".format(N),
        setup="from __main__ import underscore", number=1))
    print("loopiter: ",timeit.timeit("loopiter({})".format(N),
        setup="from __main__ import loopiter", number=1))
    print("loopiter2: ",timeit.timeit("loopiter2({})".format(N),
        setup="from __main__ import loopiter2", number=1))

J'ai également proposé une solution alternative qui s'appuie sur celle de Martelli et utilise map()pour appeler la fonction de charge utile. OK, j'ai un peu triché en ce sens que j'ai pris la liberté de faire accepter à la charge utile un paramètre qui est rejeté: je ne sais pas s'il existe un moyen de contourner cela. Néanmoins, voici les résultats:

standard:  0.8398549720004667
underscore:  0.8413165839992871
loopiter:  0.7110594899968419
loopiter2:  0.5891903560004721

donc l'utilisation de la carte donne une amélioration d'environ 30% par rapport à la boucle standard pour et 19% supplémentaires par rapport à celle de Martelli.


4

Supposons que vous ayez défini do_something comme une fonction et que vous souhaitiez l'exécuter N fois. Vous pouvez peut-être essayer ce qui suit:

todos = [do_something] * N  
for doit in todos:  
    doit()

45
Sûr. N'appelons pas simplement la fonction un million de fois, allouons également une liste d'un million d'éléments. Si le CPU fonctionne, la mémoire ne devrait-elle pas aussi être un peu stressée? La réponse ne peut pas être qualifiée de définitivement «inutile» (elle montre une approche différente et fonctionnelle), donc je ne peux pas voter contre, mais je ne suis pas d'accord et je m'y oppose totalement.
tzot

1
N'est-ce pas juste une liste de N références à la même valeur de fonction?
Nick McCurdy

mieux vaut faire fn() for fn in itertools.repeat(do_something, N)et enregistrer la pré-génération du tableau ... c'est mon idiome préféré.
F1Rumors

1
@tzot Pourquoi ce ton condescendant? Cette personne a fait des efforts pour rédiger une réponse et peut maintenant être découragée de contribuer à l'avenir. Même si cela a des implications sur les performances, c'est une option qui fonctionne et surtout si N est petit, les implications performances / mémoire ne sont pas significatives.
davidscolgan

Je suis toujours surpris de voir à quel point les développeurs Python sont obsédés par les performances :) Bien que je convienne que ce n'est pas idiomatique, et que quelqu'un de nouveau en lecture de Python, il ne comprend peut-être pas ce qui se passe aussi clairement que lors de l'utilisation d'un itérateur
Asfand Qazi

1

Qu'en est-il d'une simple boucle while?

while times > 0:
    do_something()
    times -= 1

Vous avez déjà la variable; pourquoi ne pas l'utiliser?


1
Ma seule pensée est que c'est 3 lignes de code contre une (?)
AJP

2
@AJP - Plus comme 4 lignes vs 2 lignes
ArtOfWarfare

ajoute la comparaison (temps> 0) et le décrément (temps - = 1) aux frais généraux ... donc plus lent que la boucle for ...
F1Rumeurs

@ F1Rumors Je ne l'ai pas mesuré, mais je serais surpris si des compilateurs JIT comme PyPy devraient générer du code plus lent pour une boucle while aussi simple.
Philipp Claßen
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.