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 exec开发者_StackOverflowution 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.
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).
Hope this helps you or any future viewers of this question!
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
.
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.
You can use
[1/egg for egg in eggs if egg != 0]
this will simply skip elements that are zero.
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
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.
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]
You can use generators:
def invert(xs):
for x in xs:
try:
yield x
except:
yield None
list(invert(eggs))
精彩评论