[Python-ideas] for-while statement

Masklinn masklinn at masklinn.net
Wed Feb 19 23:07:29 CET 2014


On 2014-02-19, at 14:34 , Alejandro López Correa <alc at spika.net> wrote:

> Hello,
> 
> I think adding an optional "WHILE" clause in "FOR" loops might be
> useful sometimes (shorter code and/or improved readability):

It seems judicious application of itertools can do the job.

> for #VAR# in #SEQUENCE# while #WATCHDOG_EXPRESSION#:
>  #CODE_BLOCK#
> 
> Examples:
> 
> keepRunning = True
> for i in range(100) while keepRunning:
>    keepRunning = do_some_work( i )
> 
> found = False
> for candidate in sequence while not found:
>    try:
>        process( candidate )
>        found = True
>    except InvalidCandidate:
>        pass
> 
> retryCount = 7
> for i in range(1,1+retryCount) while resource.acquire() == FAIL:
>    sleep( i**2 )
> 
> 
> At the moment, I usually implement this either with ugly breaks:
> 
> 
> for i in range(100):
>    if not do_some_work( i ):
>        break

for i in takewhile(do_some_work, range(100)):
    pass

> 
> found = False
> for candidate in sequence:
>    try:
>        process_candidate()
>    except InvalidCandidate:
>        pass
>    else:
>       found = True
>       break
> 

for candidate in sequence:
    try:
        process_candidate()
    except InvalidCandidate:
        pass
    else:
        # found
        break
else:
    # not found

> 
> Or with while loops and counters (or counting-like expressions):
> 
> 
> i = 1
> while i <= retryCount and not resource.acquired:
>    if resource.acquire() == FAIL:
>        sleep( i**2 )
>    i += 1
> 
> 
> Of course, actual code tends to be more complex, with "keepRunning"
> being modified in some branches of "IF" blocks, and there might be
> nested loops where the exit condition for the outer one is set in the
> inner loop. Compare these two examples:
> 
> found = False
> for filePath in glob( '*.data' ):
>    for line in open( filePath, 'rt' ):
>        if line.startswith( '#' ):
>            continue
>        if handle( line ):
>            found = True
>            break
>    if found:
>        break

> found = False
> for filePath in glob( '*.data' ) while not found:
>    for line in open( filePath, 'rt' ) while not found:
>        if line.startswith( '#' ):
>            continue
>        if handle( line ):
>            found = True
> 

# itertools's missing piece
flatmap = lambda fn, *it: chain.from_iterable(imap(fn, *it))

lines = flatmap(open, iglob('*.data'), repeat('rb')
non_comments = ifilter(lambda line: not line.startswith('#'), lines)
matches = ifilter(handle, non_comments)

match = next(matches, None)

Alternatively the filters could be replaced by

matches =(line for line in lines
          if not line.startswith('#')
          if handle(line))


More information about the Python-ideas mailing list