Python's iterators are great and all, but sometimes I really do want a C-style for loop - not a foreach loop. For example, I have a start date and an end date and I want to do something for every day in that range. I can do this with a while loop, of course:
current = start
while current <= finish:
do_stuff(current)
current += timedelta(1)
This wo开发者_如何学Gorks, but it's 3 lines instead of 1 (in C or C-based languages) and I often find myself forgetting to write the incrementing line, especially if the loop body is quite complex. Is there a more elegant and less error-prone way of doing this in Python?
The elegant and Pythonic way to do it is to encapsulate the idea of a range of dates in its own generator, then use that generator in your 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
prints:
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
This is similar to the answer about range
, except that the built-in range
won't work with datetimes, so we have to create our own, but at least we can do it just once in an encapsulated way.
Doing it in a compact way it's not easy in Python, as one of the basic concepts behind the language is not being able to make assignments on comparisons.
For something complex, like a date, I think that the answer of Ned is great, but for easier cases, I found very useful the itertools.count() function, which return consecutive numbers.
>>> 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
I found it less error-prone, as it's easy, as you said, to forget the 'current += 1'. To me it seems more natural to make an infinite loop and then check for an end condition.
This will work in a pinch:
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)
For the sake of iterating only, you should actually use xrange over range, since xrange will simply return an iterator, whereas range will create an actual list object containing the whole integer range from first to last-1 (which is obviously less efficient when all you want is a simple for-loop):
for i in xrange(current,finish+1, timedelta(1)):
do_stuff(i)
Additionally, there is enumerate, which returns an enumerate object that will yield an incrementing count and the value of a collection, i.e.:
l = ["a", "b", "c"]
for ii, value in enumerate(l):
print ii, value
Result:
0 a
1 b
2 c
精彩评论