Имеет ли Python какой-либо эквивалент цикла (не foreach)

Итераторы Python велики и все, но иногда мне действительно нужен цикл цикла C, а не цикл foreach. Например, у меня есть дата начала и дата окончания, и я хочу что-то делать каждый день в этом диапазоне. Я могу сделать это с помощью цикла while, конечно:

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

Это работает, но это 3 строки вместо 1 (на языках C или C), и я часто забываю писать инкрементирующую строку, особенно если тело цикла довольно сложно. Есть ли более элегантный и менее подверженный ошибкам способ сделать это в Python?

Ответы

Ответ 1

Элегантный и Pythonic способ сделать это - инкапсулировать идею ряда дат в свой собственный генератор, а затем использовать этот генератор в вашем коде:

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

печатает:

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

Это похоже на ответ о range, за исключением того, что встроенный range не будет работать с datetime, поэтому мы должны создать наш собственный, но по крайней мере мы можем это сделать только один раз в инкапсулированном путь.

Ответ 2

Выполняя это в компактном виде, это нелегко в Python, так как одна из основных концепций языка не может выполнять присвоения при сравнении.

Для чего-то сложного, как дата, я думаю, что ответ Неда велик, но для более простых случаев я нашел очень полезной функцию itertools.count(), которая возвращает последовательные числа.

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

Я нашел его менее подверженным ошибкам, так как легко, как вы сказали, забыть "current + = 1". Для меня кажется более естественным сделать бесконечный цикл, а затем проверить конечное условие.

Ответ 3

Это будет работать в крайнем случае:

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)

Ответ 4

Только для итерации вы должны использовать xrange по диапазону, так как xrange просто вернет итератор, тогда как диапазон создаст фактический объект списка, содержащий весь целочисленный диапазон от первого до последнего -1 (что, очевидно, менее эффективно, когда все, что вам нужно, это простой цикл):

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

Кроме того, имеется перечисление, которое возвращает объект перечисления, который даст инкрементный счетчик и значение коллекции, то есть:

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

Результат:

0 a
1 b
2 c