Home > OS >  Do try-except many times but raise error if one of them fails (at the end)
Do try-except many times but raise error if one of them fails (at the end)

Time:05-24

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
  • Related