-->

Python: How to tell the for loop to continue from

2019-06-15 03:59发布

问题:

Sometimes I need the following pattern within a for loop. At times more than once in the same loop:

    try:
        var = 'attempt to do something that may fail on a number of levels'
    except Exception, e:
        log(e)
        continue

Now I don't see a nice way to wrap this in a function as it can not return continue:

def attempt(this):
    try:
        return this
    except Exception, e:
        log(e)
        # 1. continue # <-- syntax error: continue not properly in loop or
        # 2. return continue # <-- invalid syntax
        # 3.
        return False # <-- this sort of works, but makes me feel powerless

If I return False than I could:

    var = attempt('to do something that may fail on a number of levels')
    if not var:
        continue

But I don't feel that does it the justice. I want to tell the for loop to continue (or fake it) from within attempt function.

回答1:

Python already has a very nice construct for doing just this and it doesn't use continue:

for i in range(10):
    try:
        r = 1.0 / (i % 2)
    except Exception, e:
        print(e)
    else:
        print(r)

I wouldn't nest any more than this, though, or your code will soon get very ugly.

In your case I would probably do something more like this as it is far easier to unit test the individual functions and flat is better than nested:

#!/usr/bin/env python

def something_that_may_raise(i):
    return 1.0 / (i % 2)

def handle(e):
    print("Exception: " + str(e))

def do_something_with(result):
    print("No exception: " + str(result))

def wrap_process(i):
    try:
        result = something_that_may_raise(i)
    except ZeroDivisionError, e:
        handle(e)
    except OverflowError, e:
        handle(e) # Realistically, this will be a different handler...
    else:
        do_something_with(result)

for i in range(10):
    wrap_process(i)

Remember to always catch specific exceptions. If you were not expecting a specific exception to be thrown, it is probably not safe to continue with your processing loop.

Edit following comments:

If you really don't want to handle the exceptions, which I still think is a bad idea, then catch all exceptions (except:) and instead of handle(e), just pass. At this point wrap_process() will end, skipping the else:-block where the real work is done, and you'll go to the next iteration of your for-loop.

Bear in mind, Errors should never pass silently.



回答2:

The whole idea of exceptions is that they work across multiple levels of indirection, i.e., if you have an error (or any other exceptional state) deep inside your call hierarchy, you can still catch it on a higher level and handle it properly.

In your case, say you have a function attempt() which calls the functions attempt2() and attempt3() down the call hierarchy, and attempt3() may encounter an exceptional state which should cause the main loop to terminate:

class JustContinueException(Exception):
    pass

for i in range(0,99):
    try:
        var = attempt() # calls attempt2() and attempt3() in turn
    except JustContinueException:
        continue # we don't need to log anything here
    except Exception, e:
        log(e)
        continue

    foo(bar)

def attempt3():
    try:
        # do something
    except Exception, e:
        # do something with e, if needed
        raise # reraise exception, so we catch it downstream

You can even throw a dummy exception yourself, that would just cause the loop to terminate, and wouldn't even be logged.

def attempt3():
    raise JustContinueException()


回答3:

Maybe you want to do continuations? You could go and look at how Eric Lippert explains them (if you are ready to have your mind blown, but in Python it could look a bit like this:

def attempt(operation, continuation):
    try:
        operation()
    except:
        log('operation failed!')
    continuation()

Inside your loop you could do:

attempt(attempt_something, lambda: foo(bar)) # attempt_something is a function


回答4:

Think that you are mapping foo on all items where attempt worked. So attempt is a filter and it's easy to write this as a generator:

def attempted( items ):
    for item in items:
        try:
            yield attempt( item )
        except Exception, e:
            log(e)

print [foo(bar) for bar in attempted( items )]


回答5:

You could use this:

for l in loop:
  attempt() and foo(bar)

but you should make sure attempt() returns True or False.

Really, though, Johnsyweb's answer is probably better.



回答6:

I wouldn't normally post a second answer, but this is an alternative approach if you really don't like my first answer.

Remember that a function can return a tuple.

#!/usr/bin/env python

def something_that_mail_fail(i):
    failed = False
    result = None
    try:
        result = 1.0 / (i % 4)
    except:
        failed = True # But we don't care
    return failed, result

for i in range(20):
    failed, result = something_that_mail_fail(i)
    if failed:
        continue
    for rah in ['rah'] * 3:
        print(rah)
    print(result)

I maintain that try ... except ... else is the way to go, and you shouldn't silently ignore errors though. Caveat emptor and all that.



回答7:

Apart from the context I just want to answer the question in a brief fashion. No, a function cannot continue a loop it may be called in. That is because it has no information about this context. Also, it would raise a whole new class of questions like what shall happen if that function is called without a surrounding loop to handle that continue?

BUT a function can signal by various means that it wants the caller to continue any loop it currently performs. One means of course is the return value. Return False or None to signal this for example. Another way of signaling this is to raise a special Exception:

class ContinuePlease(Exception): pass

def f():
    raise ContinuePlease()

for i in range(10):
    try:
        f()
    except ContinuePlease:
        continue


回答8:

put the for loop outside the try, except block ... simple... ;-)

import sys
if '3.4' in sys.version:
    from termcolor import colored

def list_attributes(module_name): '''Import the module before calling this func on it.s ''' for index, method in enumerate(dir(module_name)): try: method = str(method) module = 'email' expression = module + '.' + method print('*' * len(expression), '\n') print( str(index).upper() + '. ',colored( expression.upper(), 'red'), ' ', eval( expression ).dir() , '...' , '\n'2 ) print('' * len(expression), '\n') print( eval( expression + '.doc' ), '\n'*4, 'END OF DESCRIPTION FOR: ' + expression.upper(), '\n'*4) except (AttributeError, NameError): continue else: pass finally: pass



回答9:

Edit: Removed all that stupidity I said...

The final answer was to rewrite the whole thing, so that I don't need to code like that.