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):
    hourString = GetHourString(hour, timeFormatter, hourFP)

    # do other things with hour and hourString...

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?

  1. No comments yet.
(will not be published)