简体   繁体   中英

Simulating C-style for loops in python

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

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 function).

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.

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

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 .

I often do

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

   etc.

Heh:

def forre(a,i,c,top,increment,run):
    increment = increment.replace("++","+=1").replace("--","-=1").replace("; ","")
    while i != top:
        try: exec(run)
        except: print "error: "; print run
        try: exec(increment)
        except: print "error: "; print increment

forre("int i=",0,"; i<",6,"; i++", 
    "print i"
    )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM