2009-12-23 3 views
13

Gli iteratori di Python sono grandiosi e tutti, ma a volte desidero davvero uno stile C per il ciclo, non un ciclo foreach. Ad esempio, ho una data di inizio e una data di fine e voglio fare qualcosa per ogni giorno in quell'intervallo. Posso fare questo con un ciclo while, naturalmente:Python ha un equivalente di ciclo for (non foreach)

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

Questo funziona, ma è 3 linee invece di 1 (in C o nelle lingue C-based) e mi ritrovo spesso dimenticando di scrivere la linea di incremento, specialmente se il corpo del ciclo è piuttosto complesso. C'è un modo più elegante e meno incline agli errori di farlo in Python?

risposta

30

Il modo elegante e Pythonic per farlo è quello di incapsulare l'idea di un intervallo di date nel proprio generatore, quindi utilizzare tale generatore nel codice:

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 

stampe :

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 

Questo è simile alla risposta su range, salvo che il built-in range non funziona con datetimes, quindi abbiamo per creare il nostro, ma almeno possiamo farlo solo una volta in modo incapsulato.

+1

+1 non solo perché è l'unica risposta che ** funziona davvero ** ma anche perché è quello giusto. Seriamente, non votare le risposte che * sembrano buone * –

-2

Per motivi di iterazione solo, si dovrebbe effettivamente utilizzare xrange range, poiché xrange restituirà semplicemente un iteratore, considerando gamma creerà un oggetto lista corrente contenente l'intera gamma intero dal primo all'ultimo-1 (che è ovviamente meno efficiente quando invece si è un semplice ciclo for):

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

Inoltre, v'è enumerare, che restituisce un oggetto enumerare che produrrà un conteggio incremento e il valore di una collezione, vale a dire:

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

Risultato:

0 a 
1 b 
2 c 
+2

-1 risposte di prova prima di pubblicare. Il risultato è 'TypeError: è richiesto un intero'. Tutti gli argomenti di 'xrange()' devono essere numeri interi. –

+0

'xrange' avrebbe dovuto essere chiamato' irange' poiché restituisce un iteratore mentre 'range' dovrebbe sempre restituire una lista; l'unico vincolo su 'xrange' dovrebbe essere quello' next = start; next = next + passaggio; fino alla prossima == fine', cioè che 'start' deve essere' __add__'able a 'step' e il risultato deve essere' __cmp__'able a 'end' –

2

Effettuarlo in modo compatto non è facile in Python, in quanto uno dei concetti di base dietro la lingua non è in grado di effettuare assegnamenti sui confronti.

Per qualcosa di complesso, come una data, penso che la risposta di Ned sia ottima, ma per i casi più semplici, ho trovato molto utile la funzione itertools.count(), che restituisce numeri consecutivi.

>>> 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 

ho trovato meno soggetto a errori, come è facile, come hai detto, di dimenticare la 'corrente + = 1'. Per me sembra più naturale fare un ciclo infinito e quindi controllare la condizione finale.

+5

WTF? Perché non usare semplicemente 'for i in xrange (begin, end):'? –

1

Ciò funzionerà in un pizzico:

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)