2009-12-23 9 views
13

Les itérateurs de Python sont excellents et tout, mais parfois je veux vraiment un style C pour la boucle - pas une boucle foreach. Par exemple, j'ai une date de début et une date de fin et je veux faire quelque chose pour chaque jour dans cette gamme. Je peux le faire avec une boucle while, bien sûr:Est-ce que Python a un équivalent pour boucle (pas foreach)

current = start 
    while current <= finish: 
     do_stuff(current) 
     current += timedelta(1) 

Cela fonctionne, mais il est 3 lignes au lieu de 1 (en C ou en langues à base C) et je me surprends souvent à oublier d'écrire la ligne incrémentiel, surtout si le corps de la boucle est assez complexe. Y a-t-il une façon plus élégante et moins sujette aux erreurs de le faire en Python?

Répondre

30

La manière élégante et Pythonic de le faire est de résumer l'idée d'une plage de dates dans son propre générateur, utilisez ce générateur dans votre code:

import datetime 

def daterange(start, end, delta): 
    """ Just like `range`, but for dates! """ 
    current = start 
    while current < end: 
     yield current 
     current += delta 

start = datetime.datetime.now() 
end = start + datetime.timedelta(days=20) 

for d in daterange(start, end, datetime.timedelta(days=1)): 
    print d 

impressions :

2009-12-22 20:12:41.245000 
2009-12-23 20:12:41.245000 
2009-12-24 20:12:41.245000 
2009-12-25 20:12:41.245000 
2009-12-26 20:12:41.245000 
2009-12-27 20:12:41.245000 
2009-12-28 20:12:41.245000 
2009-12-29 20:12:41.245000 
2009-12-30 20:12:41.245000 
2009-12-31 20:12:41.245000 
2010-01-01 20:12:41.245000 
2010-01-02 20:12:41.245000 
2010-01-03 20:12:41.245000 
2010-01-04 20:12:41.245000 
2010-01-05 20:12:41.245000 
2010-01-06 20:12:41.245000 
2010-01-07 20:12:41.245000 
2010-01-08 20:12:41.245000 
2010-01-09 20:12:41.245000 
2010-01-10 20:12:41.245000 

Ceci est similaire à la réponse au sujet range, sauf que le haut-range ne fonctionne pas avec datetimes, nous avons donc pour créer le notre, mais au moins nous pouvons le faire une seule fois de manière encapsulée.

+1

+1 non seulement parce que c'est la seule réponse ** qui fonctionne réellement ** mais aussi parce que c'est la bonne. Sérieusement, ne votez pas des réponses qui * juste bon * –

-2

Par souci de itérer seulement, vous devriez réellement utiliser xrange sur toute la gamme, puisque xrange retournera simplement un iterator, alors que gamme va créer un objet de la liste réelle contenant l'ensemble entier du premier au dernier-1 (qui est évidemment moins efficace quand tout ce que vous voulez est une simple boucle):

for i in xrange(current,finish+1, timedelta(1)): 
    do_stuff(i) 

de plus, il est énumèrent, qui retourne un objet Énumérer qui donnera un compte incrémenter et la valeur d'une collection, à savoir:

l = ["a", "b", "c"] 
for ii, value in enumerate(l): 
    print ii, value 

Résultat:

0 a 
1 b 
2 c 
+2

-1 réponses test avant de publier. Le résultat est 'TypeError: un entier est requis'. Tous les arguments de 'xrange()' doivent être des entiers. –

+0

'xrange' aurait dû être nommé' irange' car il retourne un itérateur alors que 'range' devrait toujours retourner une liste; la seule contrainte sur 'xrange' devrait être' next = start; suivant = suivant + étape; jusqu'à next == end', c'est-à-dire que 'start' doit être' __add__'able à 'step' et que le résultat doit être' __cmp__'able à 'end' –

2

Le faire de manière compacte n'est pas facile en Python, car l'un des concepts de base du langage ne permet pas de faire des assignations sur les comparaisons. Pour quelque chose de complexe, comme une date, je pense que la réponse de Ned est géniale, mais pour des cas plus simples, j'ai trouvé très utile la fonction itertools.count(), qui retourne des nombres consécutifs.

>>> import itertools 
>>> begin = 10 
>>> end = 15 
>>> for i in itertools.count(begin): 
... print 'counting ', i 
... if i > end: 
...  break 
... 
counting 10 
counting 11 
counting 12 
counting 13 
counting 14 
counting 15 
counting 16 

je l'ai trouvé moins, comme il est facile sujettes à erreur, comme vous l'avez dit, d'oublier le « courant + = 1 ». Pour moi, il semble plus naturel de faire une boucle infinie et ensuite vérifier une condition finale.

+5

WTF? Pourquoi ne pas simplement utiliser 'for i in xrange (begin, end):'? –

1

Cela fonctionne dans un pincement:

def cfor(start, test_func, cycle_func): 
    """A generator function that emulates the most common case of the C for 
    loop construct, where a variable is assigned a value at the begining, then 
    on each next cycle updated in some way, and exited when a condition 
    depending on that variable evaluates to false. This function yields what 
    the value would be at each iteration of the for loop. 

    Inputs: 
     start: the initial yielded value 
     test_func: called on the previous yielded value; if false, the 
        the generator raises StopIteration and the loop exits. 
     cycle_func: called on the previous yielded value, retuns the next 
        yielded value 
    Yields: 
     var: the value of the loop variable 

    An example: 

    for x in cfor(0.0, lambda x: x <= 3.0, lambda x: x + 1.0): 
     print x # Obviously, print(x) for Python 3 

    prints out 

    0.0 
    1.0 
    2.0 
    3.0 

    """ 
    var = start 
    while test_func(var): 
     yield var 
     var = cycle_func(var)