python map exception continue mapping execution
Question:
The following example is very simple. I want to execute map() with a function which can raise Exception. It will be more clearly with an example :
number_list = range(-2,8)
def one_divide_by(n):
return 1/n
try:
for number, divide in zip(number_list, map(one_divide_by, number_list)):
print("%d : %f" % (number, divide))
except ZeroDivisionError:
# Execution is stopped. I want to continue mapping
pass
When I execute this code I get :
-2 : -0.500000
-1 : -1.000000
It’s due to the 0 in my list. I don’t want remove this 0 (because in real case I can’t know first if I will get Exception).
Do you know how to continue mapping after the exception ?
Answers:
you could catch the exception in your function (instead of in the for
loop) and return None
(or whatever you choose) if ZeroDivisionError
is raised:
def one_divide_by(n):
try:
return 1/n
except ZeroDivisionError:
return None
if you choose to return None
you need to adapt your format string; None
can not be formatted with %f
.
other values you could return (and that would be compatible with your string formatting) are float('inf')
(or float('-inf')
depending on the sign of your numerator) or float('nan')
– “infinity” or “not a number”.
here you will find some of the caveats of using float('inf')
.
You can move the try/except
block inside the function. Example –
def one_divide_by(n):
try:
return 1/n
except ZeroDivisionError:
return 0 #or some other default value.
And then call this normally, without a try/except block –
for number, divide in zip(number_list, map(one_divide_by, number_list)):
print("%d : %f" % (number, divide))
If you can replace map
instead of changing the definition of your function one_divide_by
, I would recommend more_itertools.map_except
:
from more_itertools import map_except
number_list = range(-2,8)
def one_divide_by(n):
return 1/n
for number, divide in map_except(
lambda number: (number, one_divide_by(number)),
number_list,
ZeroDivisionError
):
print(number, ':', divide)
This prints, as expected:
-2 : -0.5
-1 : -1.0
1 : 1.0
2 : 0.5
3 : 0.3333333333333333
4 : 0.25
5 : 0.2
6 : 0.16666666666666666
7 : 0.14285714285714285
The function more_itertools.map_except
accepts a function, an iterable as inputs (just like the built-in map
) and expected exceptions. When one of those exceptions is raised during the mapping process, the element is simply skipped.
This package can be installed using pip install more-itertools
, for pip users, or conda install -c conda-forge more-itertools
, for Conda users.
Another option is to define a helper function that accepts a handler for exceptions like this:
def map_e(function, *iterables, on_exception=None):
for elements in zip(*iterables):
try:
yield function(*elements)
except Exception as e:
yield on_exception(elements, exception=e)
if callable(*on_exception)
else None
list_a = [1, 2, 5, 1]
list_b = [2, -2, 6, 6]
list_c = [2, 1, 0, 8]
def sum_then_divide(a, b, c):
return (a + b) / c
def none_on_exception(*args, **kwargs):
return None
result = list(
map_e(
sum_then_divide,
list_a,
list_b,
list_c,
on_exception=none_on_exception
)
)
print(result)
No need to handle the exception in your main function, and no need to add external libraries.
As ruancomelli pointed out, map_except
can drop the offending function call instance from your resulting iterable. This may not be your the desired behaviour.
If you want the offending function call to still appear in the output stream, you can use function composition to transform the exception into something else. For instance, this wrapper:
def zero_div_protect(func):
def wrapper(*args):
try:
return func(*args)
except ZeroDivisionError:
return float('inf')
return wrapper
will take a function (such as one_divide_by
) and return a new function, wrapping the original function with a try:...except:
that returns an "exceptional value" when the exception occurs.
number_list = range(-2, 8)
def one_divide_by(n):
return 1/n
for number, divide in zip(number_list, map(zero_div_protect(one_divide_by), number_list)):
print("%d : %f" % (number, divide))
Output:
-2 : -0.500000
-1 : -1.000000
0 : inf
1 : 1.000000
2 : 0.500000
3 : 0.333333
4 : 0.250000
5 : 0.200000
6 : 0.166667
7 : 0.142857
You can also use zero_div_protect
as a "decorator", which replaces the original function with the wrapped version:
number_list = range(-2,8)
@zero_div_protect
def one_divide_by(n):
return 1/n
for number, divide in zip(number_list, map(one_divide_by, number_list)):
print("%d : %f" % (number, divide))
The output is the same.
See also: functools.wraps for more advanced usages.
The following example is very simple. I want to execute map() with a function which can raise Exception. It will be more clearly with an example :
number_list = range(-2,8)
def one_divide_by(n):
return 1/n
try:
for number, divide in zip(number_list, map(one_divide_by, number_list)):
print("%d : %f" % (number, divide))
except ZeroDivisionError:
# Execution is stopped. I want to continue mapping
pass
When I execute this code I get :
-2 : -0.500000
-1 : -1.000000
It’s due to the 0 in my list. I don’t want remove this 0 (because in real case I can’t know first if I will get Exception).
Do you know how to continue mapping after the exception ?
you could catch the exception in your function (instead of in the for
loop) and return None
(or whatever you choose) if ZeroDivisionError
is raised:
def one_divide_by(n):
try:
return 1/n
except ZeroDivisionError:
return None
if you choose to return None
you need to adapt your format string; None
can not be formatted with %f
.
other values you could return (and that would be compatible with your string formatting) are float('inf')
(or float('-inf')
depending on the sign of your numerator) or float('nan')
– “infinity” or “not a number”.
here you will find some of the caveats of using float('inf')
.
You can move the try/except
block inside the function. Example –
def one_divide_by(n):
try:
return 1/n
except ZeroDivisionError:
return 0 #or some other default value.
And then call this normally, without a try/except block –
for number, divide in zip(number_list, map(one_divide_by, number_list)):
print("%d : %f" % (number, divide))
If you can replace map
instead of changing the definition of your function one_divide_by
, I would recommend more_itertools.map_except
:
from more_itertools import map_except
number_list = range(-2,8)
def one_divide_by(n):
return 1/n
for number, divide in map_except(
lambda number: (number, one_divide_by(number)),
number_list,
ZeroDivisionError
):
print(number, ':', divide)
This prints, as expected:
-2 : -0.5
-1 : -1.0
1 : 1.0
2 : 0.5
3 : 0.3333333333333333
4 : 0.25
5 : 0.2
6 : 0.16666666666666666
7 : 0.14285714285714285
The function more_itertools.map_except
accepts a function, an iterable as inputs (just like the built-in map
) and expected exceptions. When one of those exceptions is raised during the mapping process, the element is simply skipped.
This package can be installed using pip install more-itertools
, for pip users, or conda install -c conda-forge more-itertools
, for Conda users.
Another option is to define a helper function that accepts a handler for exceptions like this:
def map_e(function, *iterables, on_exception=None):
for elements in zip(*iterables):
try:
yield function(*elements)
except Exception as e:
yield on_exception(elements, exception=e)
if callable(*on_exception)
else None
list_a = [1, 2, 5, 1]
list_b = [2, -2, 6, 6]
list_c = [2, 1, 0, 8]
def sum_then_divide(a, b, c):
return (a + b) / c
def none_on_exception(*args, **kwargs):
return None
result = list(
map_e(
sum_then_divide,
list_a,
list_b,
list_c,
on_exception=none_on_exception
)
)
print(result)
No need to handle the exception in your main function, and no need to add external libraries.
As ruancomelli pointed out, map_except
can drop the offending function call instance from your resulting iterable. This may not be your the desired behaviour.
If you want the offending function call to still appear in the output stream, you can use function composition to transform the exception into something else. For instance, this wrapper:
def zero_div_protect(func):
def wrapper(*args):
try:
return func(*args)
except ZeroDivisionError:
return float('inf')
return wrapper
will take a function (such as one_divide_by
) and return a new function, wrapping the original function with a try:...except:
that returns an "exceptional value" when the exception occurs.
number_list = range(-2, 8)
def one_divide_by(n):
return 1/n
for number, divide in zip(number_list, map(zero_div_protect(one_divide_by), number_list)):
print("%d : %f" % (number, divide))
Output:
-2 : -0.500000
-1 : -1.000000
0 : inf
1 : 1.000000
2 : 0.500000
3 : 0.333333
4 : 0.250000
5 : 0.200000
6 : 0.166667
7 : 0.142857
You can also use zero_div_protect
as a "decorator", which replaces the original function with the wrapped version:
number_list = range(-2,8)
@zero_div_protect
def one_divide_by(n):
return 1/n
for number, divide in zip(number_list, map(one_divide_by, number_list)):
print("%d : %f" % (number, divide))
The output is the same.
See also: functools.wraps for more advanced usages.