2009-12-23 10 views
13

Los iteradores de Python son geniales y todo, pero a veces realmente quiero un estilo C para bucle, no un bucle foreach. Por ejemplo, tengo una fecha de inicio y una fecha de finalización y quiero hacer algo por cada día dentro de ese rango. Puedo hacer esto con un bucle while, por supuesto:¿Python tiene algún equivalente de bucle (no para todos)

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

Esto funciona, pero es 3 líneas en lugar de 1 (en C o lenguajes basados-C) y a menudo me encuentro olvidar a escribir la línea de incrementación, especialmente si el cuerpo del bucle es bastante complejo. ¿Existe una manera más elegante y menos propensa a errores de hacer esto en Python?

Respuesta

30

La forma elegante y Pythonic de hacerlo es encapsular la idea de un rango de fechas en su propio generador, a continuación, utilizar ese generador en el código:

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 

impresiones :

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 

Esto es similar a la respuesta sobre range, excepto que la incorporada en range no funcionará con datetimes, por lo que tenemos para crear el nuestro, pero al menos podemos hacerlo solo una vez de una manera encapsulada.

+1

+1 no solo porque es la única respuesta que ** realmente funciona ** pero también porque es el correcto. En serio, no vote por las respuestas que solo * se ven bien * –

-2

En aras de la iteración única, en realidad se debe utilizar xrange sobre el rango, ya que xrange se devuelva un iterador, mientras gama creará un objeto de lista real que contiene toda la gama entera de principio a fin-1 (que obviamente es menos eficiente cuando lo que quieres es un simple bucle for):

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

Además, se enumeran, que devuelve un objeto de enumeración que producirá un recuento incremento y el valor de una colección, es decir:

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

Resultado:

0 a 
1 b 
2 c 
+2

-1 prueba respuestas antes de publicar. El resultado es 'TypeError: se requiere un entero'. Todos los argumentos de 'xrange()' deben ser enteros. –

+0

'xrange' debería haberse llamado' irange' ya que devuelve un iterador, mientras que 'range' siempre debe devolver una lista; la única restricción en 'xrange' debería ser que' next = start; siguiente = siguiente + paso; hasta el próximo == end', es decir que 'start' debe ser' __add__'able to 'step' y el resultado debe ser' __cmp__'able to 'end' –

2

Hacerlo de forma compacta no es fácil en Python, ya que uno de los conceptos básicos detrás del lenguaje es no poder hacer asignaciones en las comparaciones.

Para algo complejo, como una fecha, creo que la respuesta de Ned es genial, pero para casos más fáciles, encontré muy útil la función itertools.count(), que devuelve números consecutivos.

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

me pareció menos propenso a errores, ya que es fácil, como usted ha dicho, para olvidar el 'actual + 1 ='. Para mí, parece más natural hacer un ciclo infinito y luego verificar si hay una condición final.

+5

WTF? ¿Por qué no usar 'for i in xrange (begin, end):'? –

1

Esto funcionará en un apuro:

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) 
Cuestiones relacionadas