更多 pythonic 方式来处理嵌套的 try... 除了块?

More pythonic way to handle nested try... except blocks?

是否有更简洁或更 pythonic 的方法来执行以下操作?

try:
    error_prone_function(arg1)
except MyError:
    try:
        error_prone_function(arg2)
    except MyError:
        try:
            another_error_prone_function(arg3)
        except MyError:
            try:
                last_error_prone_function(arg4)
            except MyError:
                raise MyError("All backup parameters failed.")

基本上是:如果尝试#1 失败,请尝试#2。如果#2 失败,请尝试#3。如果#3 失败,请尝试#4。如果#4 失败,...如果#n 失败,则最后引发一些异常。

请注意,我不一定每次都调用相同的函数,也不一定每次都使用相同的函数参数。然而,我,期望每个函数都有相同的异常MyError

感谢 John Kugelman 的 post here,我决定使用它,它利用 for 循环的鲜为人知的 else 子句在整个列表具有时执行代码没有发生 break 就已经筋疲力尽了。

funcs_and_args = [(func1, "150mm"),
                  (func1, "100mm",
                  (func2, "50mm"),
                  (func3, "50mm"),
                  ]
for func, arg in funcs_and_args :
    try:
        func(arg)
        # exit the loop on success
        break
    except MyError:
        # repeat the loop on failure
        continue
else:
    # List exhausted without break, so there must have always been an Error
    raise MyError("Error text")

正如 Daniel Roseman 在下面评论的那样,注意缩进,因为 try 语句 有一个 else 子句。

基于生成器的方法可能比数据驱动的方法更灵活:

def attempts_generator():

#   try:
#       <the code you're attempting to run>
#   except Exception as e:
#       # failure
#       yield e.message
#   else:
#       # success
#       return

    try:
        print 'Attempt 1'
        raise Exception('Failed attempt 1')
    except Exception as e:
        yield e.message
    else:
        return

    try:
        print 'Attempt 2'
        # raise Exception('Failed attempt 2')
    except Exception as e:
        yield e.message
    else:
        return

    try:
        print 'Attempt 3'
        raise Exception('Failed attempt 3')
    except Exception as e:
        yield e.message
    else:
        return

    try:
        print 'Attempt 4'
        raise Exception('Failed attempt 4')
    except Exception as e:
        yield e.message
    else:
        return

    raise Exception('All attempts failed!')

attempts = attempts_generator()
for attempt in attempts:
    print attempt + ', retrying...'

print 'All good!'

我们的想法是构建一个生成器,该生成器通过重试循环遍历尝试块。

一旦生成器成功尝试,它就会停止自己的迭代并硬性 return。不成功的尝试屈服于下一个回退的重试循环。否则,如果尝试次数用完,它最终会抛出无法恢复的错误。

这里的优点是 try..excepts 的内容可以是您想要的任何内容,而不仅仅是单独的函数调用(无论出于何种原因特别尴尬)。生成器函数也可以在闭包中定义。

就像我在这里所做的那样,yield 也可以传回用于日志记录的信息。

上面的输出,顺便说一句,注意我让尝试 2 成功,如所写:

mbp:scratch geo$ python ./fallback.py
Attempt 1
Failed attempt 1, retrying...
Attempt 2
All good!

如果您取消注释尝试 2 中的加注,那么它们都会失败,您将得到:

mbp:scratch geo$ python ./fallback.py
Attempt 1
Failed attempt 1, retrying...
Attempt 2
Failed attempt 2, retrying...
Attempt 3
Failed attempt 3, retrying...
Attempt 4
Failed attempt 4, retrying...
Traceback (most recent call last):
  File "./fallback.py", line 47, in <module>
    for attempt in attempts:
  File "./fallback.py", line 44, in attempts_generator
raise Exception('All attempts failed!')
Exception: All attempts failed!

编辑:

就您的伪代码而言,这看起来像:

def attempts_generator():
    try:
        error_prone_function(arg1)
    except MyError
        yield
    else:
        return

    try:
        error_prone_function(arg2)
    except MyError
        yield
    else:
        return

    try:
        another_error_prone_function(arg3)
    except MyError:
        yield
    else:
        return

    try:
        last_error_prone_function(arg4)
    except MyError:
        yield
    else:
        return

    raise MyError("All backup parameters failed.")

attempts = attempts_generator()
for attempt in attempts:
    pass

它会让除 MyError 之外的任何异常冒出并停止整个事情。您还可以选择为每个块捕获不同的错误。