28

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

try:
    # attempt to do something that may diversely fail
except Exception as e:
    logging.error(e)
    continue

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

def attempt(x):
    try:
        raise random.choice((ValueError, IndexError, TypeError))
    except Exception as e:
        logging.error(e)
        # continue  # syntax error: continue not properly in loop
        # return continue  # invalid syntax
        return None  # this sort of works

If I return None than I could:

a = attempt('to do something that may diversely fail')
if not a:
    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.

fmalina
  • 6,120
  • 4
  • 37
  • 47
  • 4
    Why the urge to wrap the four straightforward lines of code [try-except-log-continue] into something less transparent? – NPE May 20 '11 at 11:11
  • 7
    What's wrong with raising an exception inside the function and then catching it inside your loop? – rzetterberg May 20 '11 at 11:12
  • 1
    You're doing it all wrong. If you're using a for loop as a common pattern, wrap *that* inside a function, not the loop's elements. – Andrei Sosnin May 20 '11 at 11:13
  • 1
    Looking at this a few years later, "except Exception:” or “except Exception as e:” is the most diabolical Python antipattern https://realpython.com/blog/python/the-most-diabolical-python-antipattern/ – fmalina Apr 18 '17 at 08:41

9 Answers9

16

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.

johnsyweb
  • 136,902
  • 23
  • 188
  • 247
  • 1
    @Frank Malina: Sorry to hear that. Which part does not meet your requirements? `else:` looks like the most Pythonic way to address this situation to me. – johnsyweb May 20 '11 at 12:10
  • I don't want to handle exceptions, I just want to instruct the program to continue with the next cycle of the nearest enclosing loop if exceptions arise. – fmalina May 20 '11 at 12:30
  • @Frank Malina: That sounds particularly dangerous to me, but if that's what you *really* want to do, just have `except: pass` as your exception handler. – johnsyweb May 20 '11 at 12:45
  • `pass` says: carry on in this cycle. That won't work. I want to say: skip the rest, try next cycle. – fmalina May 20 '11 at 13:04
  • 1
    @Frank Malina: If everything else in the cycle is in the `else:`-block, (wrapped in `do_something_with()` for neatness), then this skips the rest of the loop and goes to the next cycle as you wished. Try running my example code for a fuller demonstration. – johnsyweb May 20 '11 at 13:09
  • 1
    @Frank Malina, it looks like Johnsyweb's answer does exactly what you've described in your question. The code that you've written and the code that Johnsyweb has provided would work the same in Python. You really should try it out or review it carefully. – Andrei Sosnin May 20 '11 at 13:34
  • Can't put the rest of the loop in the else clause. That's not what I wanted... RE: never pass silently. Unless explicitly silenced. – fmalina May 20 '11 at 13:39
  • @Frank Malina: "Can't" or "Won't"? "explicitly silenced" doesn't mean "silence **all** errors"! It sounds like what you've asked for and what you need are not the same thing. – johnsyweb May 20 '11 at 20:58
  • Sometimes you have to silence errors. Just log them and carry on. Sometimes you don't even want to keep a log. For example if you are parsing a list of streams looking for tokens that aren't there or downloading a list of resources, some of them return 404, 410 or 500. Just move to the next item on the list. – fmalina May 21 '11 at 13:18
  • 1
    @Frank Malina: Sometimes you have to silence *specific* errors. Logging is not silencing. – johnsyweb May 21 '11 at 21:19
8

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()
Boaz Yaniv
  • 6,334
  • 21
  • 30
  • `you can still catch it on a higher level and handle it properly` --> very useful reminder :) – J0ANMM Oct 11 '17 at 07:29
4

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
Alfe
  • 56,346
  • 20
  • 107
  • 159
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
Andrei Sosnin
  • 720
  • 6
  • 16
Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
2

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.

2

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 )]
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
1

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.

Community
  • 1
  • 1
johnsyweb
  • 136,902
  • 23
  • 188
  • 247
  • Apart from returning the tuple (True, None) instead of just False, this is not much different from what I mentioned in the question. This is not instructing the for loop to continue from within the function. – fmalina May 21 '11 at 13:28
  • I'm sorry, then, I cannot help you. There is no `goto` in Python. And rightly so. – johnsyweb May 21 '11 at 21:18
0

Try the for loop outside the try, except block

This answer had Python 3.4 in mind however there are better ways in newer versions. Here is my suggestion

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

Schopenhauer
  • 1,132
  • 8
  • 8
  • isnt this much less efficient, particularly if you want to apply more than one function to the list objects? You have to loop through the list each time you want to use a function like this, whereas if you had a function that return continue you could loop once and apply the function to each element of the list – Tim Kirkwood Jan 07 '22 at 09:25
  • 1
    Python 3.10 grammar explains how generators, in particular the yield statement does what you're asking. I wrote this answer for Python 3.4. Certainly if you want to scale a code like this you should use a package optimized for this task however for a casual user on a small scale this could do the job. https://docs.python.org/3/reference/grammar.html – Schopenhauer Jan 08 '22 at 17:21
-4

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.

fmalina
  • 6,120
  • 4
  • 37
  • 47