June 29, 2005
Using generators to hide loop initialization
How often have you wanted to do a number of things in a loop, but had to move items out of the loop for performance reasons? Here's a cool use of generators that I just figured out to hide the initialization.
I was trying to use PyICU to get the locale-sensitive hour for the Chandler calendar. For instance, in some locales, the hour for 4:00pm would be "16".
Unfortunately, the interface for PyICU for this kind of thing is a little ugly:
# do some setup, initializing stuff from PyICU timeFormatter = PyICU.DateFormat.createTimeInstance() hourFP = PyICU.FieldPosition(PyICU.DateFormat.HOUR1_FIELD) # Now deal with the current hour hourdate = datetime.combine(date.today(), time(hour)) timeString = timeFormatter.format(hourdate, hourFP) (start, end) = (hourFP.getBeginIndex(), hourFP.getEndIndex()) hourString = str(timeString[start:end])
Yuck! The point here is not that PyICU is ugly, but that there is some initialization that must happen before any actual use of the variable 'hour'
The problem is that I have to do other things with 'hour' beyond just getting its time string. So my code would look like:
# initialization...
timeFormatter = PyICU.DateFormat.createTimeInstance()
hourFP = PyICU.FieldPosition(PyICU.DateFormat.HOUR1_FIELD)
for hour in range(1,24):
hourdate = datetime.combine(date.today(), time(hour))
timeString = timeFormatter.format(hourdate, hourFP)
(start, end) = (hourFP.getBeginIndex(), hourFP.getEndIndex())
hourString = str(timeString[start:end])
Again.. UGLY!
So my first thought was to combine the last 4 lines into a single function, so that I could just say
for hour in range(1,24):
hourString = GetHourString(hour, ...)
But the problem here is that GetHourString() needs context from the initialization. So it would look something like:
# initialization... timeFormatter = PyICU.DateFormat.createTimeInstance() hourFP = PyICU.FieldPosition(PyICU.DateFormat.HOUR1_FIELD)for hour in range(1,24):
# do other things with hour and hourString...
hourString = GetHourString(hour, timeFormatter, hourFP)
What if there were a way to keep the loop simple without the initialization, keep GetHourString() simple without the extra parameters, and still get the benefit of initialization outside the loop.
Enter: Generators
Instead of doing the initialization before the loop, lets hide this all in another function:
def GetLocaleHourStrings(start, end):
timeFormatter = DateFormat.createTimeInstance()
hourFP = FieldPosition(DateFormat.HOUR1_FIELD)
dummyDate = date.today()
for hour in range(start, end):
hourdate = datetime.combine(dummyDate, time(hour))
timeString = timeFormatter.format(hourdate, hourFP)
(start, end) = (hourFP.getBeginIndex(),hourFP.getEndIndex())
hourString = str(timeString)[start:end]
yield hour, hourString
Note that we do some initialization, and then yield the string each time. Nice, but how do we use it?
for hour,hourString in GetHourStrings(1, 24):
# do other things with hour and hourString...
Neat, huh?