tags:

views:

297

answers:

5

(even the title of this is going to cause flames, I realize)

Python made the deliberate design choice to have the for loop use explicit iterables, with the benefit of considerably simplified code in most cases.

However, sometimes it is quite a pain to construct an iterable if your test case and update function are complicated, and so I find myself writing the following while loops:

val = START_VAL
while <awkward/complicated test case>:
    # do stuff
    ...
    val = <awkward/complicated update>

The problem with this is that the update is at the bottom of the while block, meaning that if I want to have a continue embedded somewhere in it I have to:

  • use duplicate code for the complicated/awkard update, AND

  • run the risk of forgetting it and having my code infinite loop

I could go the route of hand-rolling a complicated iterator:

def complicated_iterator(val):
    while <awkward/complicated test case>:
         yeild val
         val = <awkward/complicated update>

for val in complicated_iterator(start_val):
    if <random check>:
         continue # no issues here
    # do stuff

This strikes me as waaaaay too verbose and complicated. Do folks in stack overflow have a simpler suggestion?

Response to comments:

@Glenn Maynard: Yes, I dismissed the answer. It's bad to write five lines if there is a way to do it in one... especially in a case that comes up all the time (looping being a common feature of Turing-complete programs).

For the folks looking for a concrete example: let's say I'm working with a custom date library. My question would then be, how would you express this in python:

for (date = start; date < end; date = calendar.next_quarter_end(date)):
    if another_calendar.is_holiday(date):
       continue
    # ... do stuff...
+1  A: 

You could use a try/finally clause to execute the update:

val = START_VAL

while <awkward/complicated test case>:
    try:
        # do stuff
        continue

    finally:
        val = <awkward/complicated update>

Caveat: this will also execute the update statement if you do a break.

John Kugelman
+3  A: 

I'm a little confused: you have a complicated while expression, and a complicated next expression, but they fit nicely into a C for loop? That doesn't make sense to me.

I recommend the custom iterator approach. You will likely find other uses for the iterator, and encapsulating the iteration is good practice anyway.

UPDATE: Using your example, I would definitely make a custom iterator. It seems perfectly natural to me that a calendar would be able to generate a series of quarterly dates:

class Calendar:
    # ...

    def quarters(self, start, end):
        """Generate the quarter-start dates between `start` and `end`."""
        date = start
        while date < end:
            yield date
            date = self.next_quarter_end(date)


for date in calendar.quarters(start, end):
    if another_calendar.is_holiday(date):
       continue
    # ... do stuff...

This seems like a wonderful abstraction for your calendar class to provide, and I bet you'll use it more than once.

Ned Batchelder
Hi Ned, I added a comment hanging off of the main question with an example of an "awkward" next expression that's still relatively compact.
YGA
@YGA: I've updated my answer.
Ned Batchelder
The problem is that there are all sorts of conceivable sequences of dates, and anyway it's not necessarily clear that you control the calendar class, so then you need to write a wrapper...
YGA
@YGA: yes, that's all true. But the wrapper is just as simple (search and replace my quarters method: /self/calendar/). And just because there are many of them doesn't mean you shouldn't abstract them out. THe question is: will you need to use a sequence of quarter-ending dates in more than one place or not? If yes, then you should abstract it into a generator.
Ned Batchelder
Hard to argue with the notion of abstracting duplicate code. But the intent of my original question is to really move the abstraction up one level higher: why do I have to write essentially the same iterator code ("quarters"/"years"/"Jewish high holidays") for each different iterating strategy, when the C-style for loop applies to every conceivable test/update function?
YGA
+5  A: 

This is the best i can come up with:

def cfor(first,test,update):
    while test(first):
        yield first
        first = update(first)

def example(blah):
    print "do some stuff"
    for i in cfor(0,lambda i:i<blah,lambda i:i+1):
        print i
    print "done"

I wish python had a syntax for closured expressions.

Edit: Also, note that you only have to define cfor once (as opposed to your complicated_iterator funtion).

David X
I can't understand why Guido is down on `lambda`, it's so handy!
Mark Ransom
This is pretty handy!
YGA
For the record, I ended up simply adding cfor (same name even) to our central tree and already have used it in two separate scripts. Thanks!
YGA
+5  A: 

What about:

date = start

while date < end:

    if not another_calendar.is_holiday(date):
        # ... do stuff...

    date = calendar.next_quarter_end(date)

But if you use that particular construct often, you're better off defining the generator once and re-using it as you did in your question.

(The fact is, since they're different languages, you can't possibly have every construct in C map to a more compact construct in Python. It's like claiming to have a compression algorithm that works equally well on all random inputs.)

detly
C's for loop is, for most intents and purposes, just syntactic sugar for a while loop (including C's own while loop). The example code given at the bottom of the OP's question is equivalent to the while loop shown here, which to me is approximately as succinct and easy to read as the C for loop.
John Y
I mostly agree, although where C dialects allow `for(int idx = 0; etc)` this has the benefit of restricting the counter scope to the loop only. This is irrelevant in Python where `for` does not create a new scope. (Also note that my remark about compactness is not a value judgement ie. I'm not saying that "compact == better", even if that seems to be the OP's criteria.)
detly
I do this in some cases, but it's reasonably common for me to have all sorts of "test cases" in the body of my while loop. In some sense, I feel that's why the "continue" construct was invented; because embedding all this logic in if statements at the top of the loop is very awkward...
YGA
Nothing against `continue` - it's a perfectly cromulent keyword. If the code reads better with it, then use it :)
detly
+2  A: 

I often do

while True:
   val = <awkward/complicated update>
   if not val:
     break

   etc.
Chris AtLee