Python中简洁的异常处理

4
我喜欢避免“三思而后行”的范例,因为我重视易读的代码。在某些情况下,我无法预测是否会发生错误,比如资源可用性或内存不足的错误。我还没有找到一种干净的编写重复或冗长的代码来处理这些情况的方法。
下面的例子稍微容易理解,但是重复的代码是不可接受的。
try:
    myobject.write(filename)
except OSError:
    if prompt("%s is in use by another application." +
              "Close that application and try again.") == "Try again":
        myobject.write(filename) #repeated code

为了去除重复代码,我必须添加几行代码并缩进所有内容,这会降低可读性。
success = False
while not success:
    try:
        myobject.write(filename)
        success = True
    except OSError:
        if prompt("%s is in use by another application." +
                  "Close that application and try again.") != "Try again":
            break

有没有一种更短的Python写法,不需要重复代码?

1
你看过这个吗?https://dev59.com/1XI95IYBdhLWcg3w8iv1 - Hitesh Dharamdasani
你为什么要两次写入文件? - Padraic Cunningham
在这个例子中,如果文件无法成功写入(另一个应用程序具有独占写访问权限),则会引发OSError。 - IceArdor
@Hitesh 感谢提供链接。如果其他人在未来偶然发现这篇文章,可以参考以下链接:https://wiki.python.org/moin/PythonDecoratorLibrary#Retry 和 https://dev59.com/vnRB5IYBdhLWcg3wn4fc。对于重复的问题,我表示抱歉。 - IceArdor
3个回答

6

除了改为 while True,您还可以添加一个 retry 装饰器,并将可重试的代码移至由 retry 装饰的函数中:

from functools import wraps
from functools import update_wrapper


def retry(prompt_text="An error occured! Retry (y/n)?", 
          prompt_match='y',
          exception=Exception):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            while True:
                try:
                    ret = func(*args, **kwargs)
                    break
                except exception:
                    if raw_input(prompt_text) == prompt_match:
                        ret = None
                        break
            return ret 
        return update_wrapper(wrapper, func)
    return decorator

@retry(prompt_text="your prompt: ", prompt_match="quit", exception=OSError)
def do_write(myobject, filename):
    myobject.write(filename)

if __name__ == "__main__":
    myobject = ...
    filename = ...
    do_write(myobject, filename) # This will be retried.

如果您在多个地方使用此模式,则值得花费精力。

这只有在使用此模式的地方超过一个时才值得这样做。

1
哈哈 +1 我也想到了同样的解决方案,但你比我快了大约1分钟!也许我们可以合并我们答案的特点?请参见:http://codepad.org/u3BYDSGp - James Mills
1
+1 装饰器是 Pythonic 的解决方案!唯一(小)的事情是我会改变 wrapper() 接受“提示”和“再试一次”的文本,使其更通用并可与其他函数一起使用。 - Nir Alfasi
1
@JamesMills 谢谢!我已经更新了装饰器以包含您的功能,并受到启发添加了另外几个功能。 - dano
1
太棒了 :) 我喜欢我们的联合回答!可惜我们都不能得到声望的积分 :P - James Mills
感谢James Mills和dano的合作回答。我希望SO不要太注重分数。包装器方法对我的目的来说足够通用,减少了重复代码,并通过将控制流程与最终结果分离来提高可读性。某种形式的retry_on_OSError(someobj.write(filename))也可能有效。 - IceArdor

2
有没有一种更短的Python写法可以避免重复代码?
实际上没有。你可能可以使用一个“while True:”循环,并消除“success”变量:
while True:
    try:
        myobject.write(filename)
        break
    except OSError:
        if prompt("%s is in use by another application."
                  "Close that application and try again.") != "Try again":
            break

但这已经是你在保持可读性的前提下所能达到最紧凑的程度了。

另外,你会注意到我在 if 语句那行移除了 +。这是不必要的,因为相邻的字符串字面量会自动连接。


谢谢。虽然“While True”和“break”稍微短一些,但我希望有更短的方式。将重试代码移到装饰器中似乎更清晰,因为它将“最终结果”与控制流分开了。 - IceArdor
(即使总行数更多) - IceArdor

1
您可以使用一个接受参数作为输入的装饰器类来包装函数,以便通用化异常处理:
class exception_handler(object):

    def __init__(self, prompt_text, prompt_match, exception):
        self.prompt = prompt_text
        self.match = prompt_match
        self.exception = exception

    def __call__(self, f):
        def wrapped_f(*args, **kwargs):
            while True:
                try:
                    f(*args, **kwargs)
                    break
                except self.exception:
                    if raw_input(self.prompt) == self.match:
                        break       
        return wrapped_f


@exception_handler("your prompt (type 'quit' to exit): ", "quit", OSError)
def f(filename):
    print("before writing to file: {}".format(filename))
    # myobject.write(filename)
    raise OSError("testing...")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接