184

I have some a list comprehension in Python in which each iteration can throw an exception.

For instance, if I have:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

I'll get a ZeroDivisionError exception in the 3rd element.

How can I handle this exception and continue execution of the list comprehension?

The only way I can think of is to use a helper function:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

But this looks a bit cumbersome to me.

Is there a better way to do this in Python?

Note: This is a simple example (see "for instance" above) that I contrived because my real example requires some context. I'm not interested in avoiding divide by zero errors but in handling exceptions in a list comprehension.

martineau
  • 119,623
  • 25
  • 170
  • 301
Nathan Fellman
  • 122,701
  • 101
  • 260
  • 319
  • 6
    There's a [PEP 463](https://www.python.org/dev/peps/pep-0463/) to add an expression to handle exceptions. In your example it would be `[1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]`. But it's still in draft mode. My gut feeling is that it's not going to be accepted. Imho expressions can get too messy (checking multiple exceptions, having more complex combinations (multiple logical operators, complex comprehensions, etc) – cfi Jun 06 '16 at 07:44
  • 1
    Note that for this *specific* example, you could use a numpy `ndarray` with appropriate settings in `np.seterr`. That would result in `1/0 = nan`. But I realise that doesn't generalise to other situations where this need arises. – gerrit Jul 25 '16 at 19:00

7 Answers7

178

I realize this question is quite old, but you can also create a general function to make this kind of thing easier:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

Then, in your comprehension:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

You can of course make the default handle function whatever you want (say you'd rather return 'None' by default).

Note: in python 3, I would make the 'handle' argument keyword only, and put it at the end of the argument list. This would make actually passing arguments and such through catch much more natural.

Update (9 years later...): For Python 3, I just meant switch *args and handle, so you can specify arguments to the function without specifying handle. A minor convenience:

def catch(func, *args, handle=lambda e : e, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

This is helpful when using a defined function in the comprehension:

from math import log    
eggs = [1,3,0,3,2]
[catch(log, egg) for egg in eggs]
[0.0, 1.0986122886681098, ValueError('math domain error'), 1.0986122886681098, 0.6931471805599453]

Under the Python 2 version, we would've had to pass in handle before the egg.

starball
  • 20,030
  • 7
  • 43
  • 238
Bryan Head
  • 12,360
  • 5
  • 32
  • 50
  • 3
    extrememly useful, thanks. While I agree with the theoretical comments, this shows a practical approach to solving a problem I've had repeatedly. – Paul Mar 24 '16 at 13:07
  • 2
    Greate answer. One mod I'd suggest is passing `args` and `kwargs` through to handle as well. That way you could return say `egg` instead of a hardcoded `0`, or an exception as you are doing. – Mad Physicist Sep 06 '16 at 14:59
  • 3
    You might also want the exception type as an optional argument (can exception types be parametrised?), so that unexpected exceptions are thrown upwards rather than ignoring all exceptions. – 00prometheus Nov 12 '17 at 15:09
  • 5
    @Bryan, can you provide code for "in python 3, I would make the 'handle' argument keyword only, and put it at the end of the argument list." tried placing `handle` after `**kwarg` and got a `SyntaxError`. Do you mean to dereference as `kwargs.get('handle',e)`? – alancalvitti Feb 12 '19 at 17:38
  • I'd like to see the Python 3 variant as well, that is confusing me. – JBCP Jun 15 '21 at 16:56
  • 2
    @JBCP: Updated my answer for Python 3. Other comments offer good additions as well, but keeping the answer simple. – Bryan Head Sep 12 '21 at 15:41
139

There is no built-in expression in Python that lets you ignore an exception (or return alternate values &c in case of exceptions), so it's impossible, literally speaking, to "handle exceptions in a list comprehension" because a list comprehension is an expression containing other expression, nothing more (i.e., no statements, and only statements can catch/ignore/handle exceptions).

Function calls are expression, and the function bodies can include all the statements you want, so delegating the evaluation of the exception-prone sub-expression to a function, as you've noticed, is one feasible workaround (others, when feasible, are checks on values that might provoke exceptions, as also suggested in other answers).

The correct responses to the question "how to handle exceptions in a list comprehension" are all expressing part of all of this truth: 1) literally, i.e. lexically IN the comprehension itself, you can't; 2) practically, you delegate the job to a function or check for error prone values when that's feasible. Your repeated claim that this is not an answer is thus unfounded.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 20
    I see. So a complete answer would be that I should either: 1. use a function 2. not use list comprehension 3. try to prevent the exception rather than handling it. – Nathan Fellman Oct 06 '09 at 22:15
  • 9
    I do not see "not using list comprehensions" as a part of the answer to "how to handle exceptions in list comprehensions", but I guess you could reasonably see it as a possible consequence of "_lexically IN_ the LC, it's not possible to handle exceptions", which is indeed the first part of the literal answer. – Alex Martelli Oct 06 '09 at 22:26
  • Can you catch an error in a generator expression or generator comprehension? –  Jul 05 '17 at 22:51
  • 1
    @AlexMartelli, would an except clause be that difficult to work into future python versions? `[x[1] for x in list except IndexError pass]`. Couldn't the interpreter create a temporary function to try `x[1]`? – alancalvitti Jun 17 '19 at 14:03
  • @Nathan, 1,2,3 above turn into tremendous headaches in functional data flows where 1. one wants typically to inline functions via lambdas; 2. alternative is using lots of nested for loops which violate functional paradigm and lead to error prone code; 3. often the errors are ad-hoc and latent complex datasets which are , as the latin word for data means, given, so cannot be easily prevented. – alancalvitti Jun 17 '19 at 14:06
21

You can use

[1/egg for egg in eggs if egg != 0]

this will simply skip elements that are zero.

Peter
  • 127,331
  • 53
  • 180
  • 211
13

No there's not a better way. In a lot of cases you can use avoidance like Peter does

Your other option is to not use comprehensions

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Up to you to decide whether that is more cumbersome or not

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
7

I think a helper function, as suggested by the one who asks the initial question and Bryan Head as well, is good and not cumbersome at all. A single line of magic code which does all the work is just not always possible so a helper function is a perfect solution if one wants to avoid for loops. However I would modify it to this one:

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

The output will be this [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. With this answer you are in full control to continue in any way you want.

Alternative:

def spam2(egg):
    try:
        return 1/egg 
    except ZeroDivisionError:
        # handle division by zero error        
        return ZeroDivisionError

Yes, the error is returned, not raised.

Elmex80s
  • 3,428
  • 1
  • 15
  • 23
  • 1
    Nice. This looks very similar to the `Either` type in some functional programming languages (like Scala), where an `Either` can contain a value of one type or another, but not both. The only difference is that in those languages it is idiomatic to put the error on the left side and the value on the right side. [Here's an article with more information](https://alvinalexander.com/scala/scala-either-left-right-example-option-some-none-null). – Alex Palmer Sep 09 '18 at 13:17
4

I didn't see any answer mention this. But this example would be one way of preventing an exception from being raised for known failing cases.

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]
Slakker
  • 171
  • 2
  • 7
  • Isn't that the same as this answer? https://stackoverflow.com/a/1528244/1084 – Nathan Fellman Oct 27 '19 at 12:50
  • 1
    There's a subtle difference. The filtering is applied on the output rather than the input list. As you can see in the posted example, I've denoted "None" for the case that might cause an exception. – Slakker Oct 28 '19 at 15:58
0

You can use generators:

def invert(xs):
    for x in xs:
        try:
            yield x
        except:
            yield None

list(invert(eggs))
Maciek D.
  • 2,754
  • 1
  • 20
  • 17