So I have say 3 functions I would like to run. Each of them can fail. I would like to built try-except
around them to:
- let as many as possible out of 3 run AND
- raise an error at the end if any of them failed. Is this possible?
The below code fails as operation D (in the middle fails) so E is never reached:
try:
c = 3 6
print(c)
except TypeError:
raise TypeError("Wrong type provided in operation C")
try:
d = 3 '6'
print(d)
except TypeError:
raise TypeError("Wrong type provided in operation D")
try:
e = 7 5
print(e)
except TypeError:
raise TypeError("Wrong type provided in operation E")
CodePudding user response:
def f1():
print("f1")
def f2():
raise TypeError
print("f2")
def f3():
print("f3")
err = None
for f in [f1, f2, f3]:
try:
f()
except TypeError as e:
# store first error
if not err:
err = e
if err:
raise err
Output:
f1
f3
[...]
TypeError
If your functions take arguments, you can loop over
[(f1, f1_args, f1_kwargs), (f2, f2_args, f2_kwargs), (f3, f3_args, f3_kwargs)]
Inspired by a comment I tried to come up with a nifty context manager that sort of raises all the exceptions in a nested fashion upon exiting. Comments are welcome.
class ErrorCollector:
def __init__(self):
self.errors = []
def exec(self, f, suppress_types=None, *args, **kwargs):
suppress_types = tuple(suppress_types) if suppress_types else ()
try:
return f(*args, **kwargs)
except suppress_types as e:
self.errors.append(e)
def _raise_all(self, errors):
if len(errors) == 1:
raise errors[0]
for e in errors:
try:
raise e
except type(e):
self._raise_all(errors[1:])
def __enter__(self):
return self
def __exit__(self, exctype, excinst, exctb):
if excinst is not None:
self.errors.append(excinst)
self._raise_all(self.errors)
def f1():
print('f1')
def f2():
raise TypeError('TypeError in f2')
print('f2')
def f3():
raise ValueError('ValueError in f3')
print('f3')
def f4():
raise TypeError('TypeError in f4')
print('f4')
def f5():
print('f5')
def f6():
raise ZeroDivisionError('ZeroDivisionError in f6')
print('f6')
def f7():
print('f7')
Now you can use:
suppress = [TypeError, ValueError]
with ErrorCollector() as ec:
for f in (f1, f2, f3, f4, f5, f6, f7):
ec.exec(f, suppress)
with the output:
f1
f5
[...]
TypeError: TypeError in f2
During handling of the above exception, another exception occurred:
[...]
ValueError: ValueError in f3
During handling of the above exception, another exception occurred:
[...]
TypeError: TypeError in f4
During handling of the above exception, another exception occurred:
[...]
ZeroDivisionError: ZeroDivisionError in f6
Note that f7
is not executed because the ZeroDivisionError
was not suppressed.
CodePudding user response:
Other responses have provided ad-hoc handlers of various manners. An interesting alternative if this is a recurring need is to build a context manager similar to contextlib.suppress, something along the lines of (untested):
class Suppressor:
def __init__(self, *to_suppress):
self.to_suppress = to_suppress # empty = suppress all
self.exceptions = []
def __enter__(self):
pass
def __exit__(self, etype, val, _):
if etype is not None:
# if the exception is selected
if not self.to_suppress or isinstance(val, self.to_suppress):
# store and suppress it
self.exceptions.append(val)
return True
# otherwise ignore
Usage:
suppressor = Suppressor(TypeError)
with suppressor:
c = 3 6
print(c) # 9
with suppressor:
d = 3 '6' # error (suppressed)
print(d)
with suppressor:
e = 7 5
print(e) # 12
if suppressor.exceptions:
# TypeError("unsupported operand type(s) for : 'int' and 'str'")
print(repr(suppressor.exceptions[0]))
with suppressor:
# ValueError: invalid literal for int() with base 10: 'a'
e = 7 int('a')
CodePudding user response:
The most simple would be:
errors = 0
try:
c = 3 6
print(c)
except TypeError:
errors =1
try:
d = 3 '6'
print(d)
except TypeError:
errors =1
try:
e = 7 5
print(e)
except TypeError:
errors =1
if errors!=0:
raise TypeError("Wrong type provided in some operation")
If you want to get which operation threw TypeError you can assign it in 3 different variable.
Hope this helps!
CodePudding user response:
You could collect the exceptions. Then, you can actually reraise them rather than building a new exception, by having access to each.
exceptions = []
try:
raise ValueError("thing 1 error") # simulate error in first function
except ValueError as exc:
exceptions.append(exc)
try:
raise ValueError("thing 2 error") # simulate error in second function
except ValueError as exc:
exceptions.append(exc)
try:
raise ValueError("thing 3 error") # simulate error in third function
except ValueError as exc:
exceptions.append(exc)
You can then optionally raise a new exception. Something like:
if exceptions:
raise Exception("something went wrong")
Which gives:
Traceback (most recent call last):
File "./main.py", line 21, in <module>
raise Exception("something went wrong")
Exception: something went wrong
Or, you can directly access any or all of these exceptions. Something like:
raise exceptions[1]
Which gives:
Traceback (most recent call last):
File "./main.py", line 25, in <module>
raise exceptions[1]
File "./main.py", line 11, in <module>
raise ValueError("thing 2 error") # simulate error in second function
ValueError: thing 2 error