66 votes

Un meilleur moyen pour une boucle 'pour' en Python

Nous savons tous que la façon la plus courante de l'exécution d'une instruction d'un certain nombre de fois en Python est d'utiliser un for boucle.

La façon de le faire est,

# I am assuming iterated list is redundant.
# Just the number of execution matters.
for _ in range(count):
    pass

Je crois que personne ne fera valoir que le code ci-dessus est la mise en commun, mais il est une autre option. À l'aide de la vitesse de la liste Python création en multipliant les références.

# Uncommon way.
for _ in [0] * count:
    pass

Il y a aussi le vieux - while moyen.

i = 0
while i < count:
    i += 1

J'ai testé le temps d'exécution de ces approches. Voici le code.

import timeit

repeat = 10
total = 10

setup = """
count = 100000
"""

test1 = """
for _ in range(count):
    pass
"""

test2 = """
for _ in [0] * count:
    pass
"""

test3 = """
i = 0
while i < count:
    i += 1
"""

print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))

# Results
0.02238852552017738
0.011760978361696095
0.06971727824807639

Je ne voudrais pas lancer le sujet si il y avait une petite différence, mais il peut être vu que la différence de vitesse est de 100%. Pourquoi ne pas Python encourager une telle utilisation si la deuxième méthode est beaucoup plus efficace? Est-il un meilleur moyen?

Le test est fait avec Windows 10 et Python 3.6.

Suivant @Tim Peters suggestion,

.
.
.
test4 = """
for _ in itertools.repeat(None, count):
    pass
"""
print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test4, setup=setup).repeat(repeat, total)))

# Gives
0.02306803115612352
0.013021619340942758
0.06400113461638746
0.008105080015739174

Qui offre une bien meilleure façon, et ce joli beaucoup de réponses à ma question.

Pourquoi est-ce plus rapide que l' range, puisque les deux sont des générateurs. Est-ce parce que la valeur ne change jamais?

93voto

Tim Peters Points 16225

À l'aide de

for _ in itertools.repeat(None, count)
    do something

est la non-évidence la façon d'obtenir le meilleur de tous les mondes: un petit constante exigence d'espace, et pas de nouveaux objets créés par itération. Sous les couvertures, le code C pour repeat utilise un natif C de type entier (pas un Python objet integer!) pour garder une trace du comte restants.

Pour cette raison, le comte besoin pour s'intégrer dans la plate-forme C, ssize_t type, qui est généralement à la plupart des 2**31 - 1 sur un 32 bits de la boîte, et ici sur une version 64 bits de zone:

>>> itertools.repeat(None, 2**63)
Traceback (most recent call last):
    ...
OverflowError: Python int too large to convert to C ssize_t

>>> itertools.repeat(None, 2**63-1)
repeat(None, 9223372036854775807)

Ce qui est très grand pour mes boucles ;-)

11voto

HyperNeutrino Points 3331

La première méthode (en Python 3) crée un objet de la plage, ce qui peut parcourir la gamme de valeurs. (C'est comme un générateur d'objet, mais vous pouvez itérer plusieurs fois.) Il ne prend pas beaucoup de mémoire, car il ne contient pas l'ensemble de la gamme de valeurs, un courant et une valeur maximale, où elle continue à augmenter par la taille du pas (par défaut 1) jusqu'à ce qu'il atteint ou passe le maximum.

Comparer la taille de l' range(0, 1000) de la taille de l' list(range(0, 1000)): Essayer en Ligne!. Le premier est très efficace en terme de mémoire; il faut seulement 48 octets indépendamment de la taille, alors que l'ensemble de la liste augmente de façon linéaire en termes de taille.

La seconde méthode, bien que plus rapide, ce mémoire dont je parlais dans la dernière année. (De plus, il semble que, bien qu' 0 prend de 24 octets, et None prend 16, tableaux de 10000 de chacun ont la même taille. Intéressant. Probablement parce qu'ils sont des pointeurs)

Fait intéressant à noter, [0] * 10000 est plus petit que list(range(10000)) par environ 10000, le type qui a du sens parce que dans la première, tout est de la même valeur primitive de sorte qu'il peut être optimisé.

Le troisième est aussi agréable car il n'a pas besoin d'une autre pile de valeur (alors que l'appelant range nécessite un autre endroit sur la pile d'appel), cependant, comme il est 6 fois plus lent, c'est pas la peine que.

Le dernier pourrait être le plus rapide, juste parce qu' itertools est cool de cette façon :P je pense qu'il utilise des C-bibliothèque optimisations, si je me souviens bien.

1voto

Darkonaut Points 2113

Cette réponse fournit une construction de boucle pour plus de commodité. Pour plus d'informations sur la mise en boucle avec itertools.repeat recherchez la réponse de Tim Peters ci - dessus , la réponse d'Alex Martelli ici et celle de Raymond Hettinger ici .

 # loop.py

"""
Faster for-looping in CPython for cases where intermediate integers
from `range(x)` are not needed.

Example Usage:
--------------

from loop import loop

for _ in loop(10000):
    do_something()

# or:

results = [calc_value() for _ in loop(10000)]
"""

from itertools import repeat
from functools import partial

loop = partial(repeat, None)
 

0voto

Mr. bug Points 189

Les deux premières méthodes nécessité d'allouer des blocs de mémoire pour chaque itération, et le troisième voudrais juste faire une étape pour chaque itération.

La gamme est lent fonction, et je l'utilise seulement quand je dois courir petit code qui ne nécessite pas de vitesse, par exemple, range(0,50). Je pense que vous ne pouvez pas comparer les trois méthodes; ils sont totalement différents.

Selon un commentaire ci-dessous, le premier cas n'est valable que pour Python 2.7, en Python 3, il fonctionne comme xrange et de ne pas allouer un bloc pour chaque itération. Je l'ai testé, et il est bon.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X