offeltoffel
offeltoffel

Reputation: 2801

"Deep return" all the way back to first function call

Suppose I have two (or more) functions. One is higher level, processing stuff by calling different other functions. If one of these functions fails (e.g. Math error), I want to "abort" not only that function, but also the ones that called it. In my case I need this to escape an error and avoid having the rest of all calculations done, without quitting the whole process (which would also terminate my GUI). Instead, I want the script to continue running, but back on the upmost level.

This is an abstraction of my original problem:

def levelA(x):
    xx = levelB(x=x)
    xx *= 2
    return xx

def levelB(x):
    if x==0: return
    y = 10 / x
    return y

x = 0
print (levelA(x=x))

Initializing x with the value 0 would cause a crash in levelB when 10 is divided by x. So I first make sure that x is different from 0. If it's not, the function is aborted by calling return. Now levelA wants to continue with the doubling of the result of levelB which, in the case x=0, is "None" and there we go with another crash.

Of course, what I can do is inserting the line

if xx is None: return

before doing xx *= 2. But in my real case, there is not just one extra level, but 2 or even 3 and there are many different functions that are called. I want to avoid checking each and every output of the function for errors.

So my question is: Can I somehow go all the way back to the first function call and skip the ones that were in between? Something like a "deep return"?

Upvotes: 3

Views: 315

Answers (3)

MartinLeitgeb
MartinLeitgeb

Reputation: 63

how about try/catch?

def levelA(x):
    xx = levelB(x=x)
    xx *= 2
    return xx

def levelB(x):
    if x==0: raise ZeroDivisionError
    y = 10 / x
    return y

try:
    x = 0
    print (levelA(x=x))
except ZeroDivisionError:
    // error handling or other stuff

Upvotes: 3

MSeifert
MSeifert

Reputation: 152725

Initializing x with the value 0 would cause a crash in levelB when 10 is divided by x. So I first make sure that x is different from 0. If it's not, the function is aborted by calling return.

That's a good summary but you reached the wrong conclusion. You shouldn't return, you should raise an Exception. Then you can use a try, except clause wherever you want to handle that:

def levelA(x):
    xx = levelB(x=x)
    xx *= 2
    return xx

def levelB(x):
    if x==0: raise ValueError
    y = 10 / x
    return y

x = 0
try:
    print(levelA(x=x))
except ValueError:
    print('x was invalid! Wanna try again?')

Even better would be to just do the division and catch the Exception raised there:

def levelA(x):
    xx = levelB(x=x)
    xx *= 2
    return xx

def levelB(x):
    y = 10 / x
    return y

x = 0
try:
    print(levelA(x=x))
except ZeroDivisionError:
    print('x was invalid! Wanna try again?')

Upvotes: 1

holdenweb
holdenweb

Reputation: 37103

Exceptions were invented to handle the situation you describe. So, for example:

In [72]: def f1(x):
    ...:     return f2(x)
    ...:

In [73]: def f2(x):
    ...:     return f3(x)
    ...:

In [74]: def f3(x):
    ...:     if x > 0:
    ...:         return x
    ...:     else:
    ...:         raise ValueError("f3 called with negative argument")
    ...:

In [75]: try:
    ...:     print(f1(-2))
    ...: except ValueError as e:
    ...:     print(e)
    ...:
f3 called with negative argument

Upvotes: 3

Related Questions