Предположим, у меня есть две (или более) функции. Один - более высокий уровень, обрабатывающий вещи, вызывая другие функции. Если одна из этих функций выходит из строя (например, математическая ошибка), я хочу «прервать» не только эту функцию, но и те, которые ее вызвали. В моем случае мне нужно это, чтобы избежать ошибки и избежать выполнения всех остальных вычислений, не выходя из всего процесса (что также приведет к завершению моего GUI). Вместо этого я хочу, чтобы скрипт продолжал работать, но вернулся на самый верхний уровень.

Это абстракция моей первоначальной проблемы:

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))

Инициализация x со значением 0 вызовет сбой в levelB, если 10 разделить на x. Поэтому я сначала проверяю, что x отличается от 0. Если это не так, функция прерывается вызовом return. Теперь levelA хочет продолжить с удвоением результата levelB, который в случае x=0 равен "None", и мы переходим к другому сбою.

Конечно, я могу вставить строку

if xx is None: return

Прежде чем делать xx *= 2. Но в моем реальном случае есть не только один дополнительный уровень, но 2 или даже 3, и есть много различных функций, которые вызываются. Я хочу избежать проверки каждого выхода функции на наличие ошибок.

Итак, мой вопрос: могу ли я как-нибудь вернуться к первому вызову функции и пропустить те, которые были между ними? Что-то вроде «глубокого возвращения»?

3
offeltoffel 28 Авг 2017 в 16:16

3 ответа

Лучший ответ

Исключения были придуманы, чтобы справиться с ситуацией, которую вы описываете. Так, например:

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
3
holdenweb 28 Авг 2017 в 13:23

Инициализация x значением 0 вызовет сбой на уровне B, если 10 разделить на x. Поэтому я сначала проверяю, что x отличается от 0. Если это не так, функция прерывается вызовом return.

Это хорошее резюме, но вы пришли к неверному выводу. Вы не должны return, вы должны поднять Exception. Затем вы можете использовать предложение try, except, где бы вы ни хотели это обработать:

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?')

Еще лучше было бы просто сделать деление и поймать исключение, поднятое там:

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?')
1
MSeifert 28 Авг 2017 в 13:25

Как насчет попробовать / поймать?

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
3
MartinLeitgeb 28 Авг 2017 в 13:40