重新尝试代码时很好的异常处理

时间:2009-08-27 20:19:49

标签: python exception exception-handling code-formatting

我有一些测试用例。测试用例依赖于需要时间来计算的数据。为了加快测试速度,我已经缓存了数据,因此无需重新计算。

我现在有foo(),它会查看缓存的数据。我不能提前告诉它会看到什么,因为这在很大程度上取决于测试用例。

如果测试用例失败导致它找不到正确的缓存数据,我不希望它失败 - 我希望它计算数据,然后再试一次。我也不知道特别是什么异常会导致丢失数据。

我的代码现在看起来像这样:

if cacheExists:
    loadCache()
    dataComputed = False
else:
    calculateData()
    dataComputed = True

try:
    foo()
except:
    if not dataComputed:
        calculateData() 
        dataComputed = True
        try:
            foo()
        except:
            #error handling code
    else:
        #the same error handling code

重新构建此代码的最佳方法是什么?

5 个答案:

答案 0 :(得分:4)

我不同意现有答案中的关键建议,这基本上归结为在Python中处理异常,就像在C ++或Java中那样 - 这不是Python中的首选样式,通常是古老的想法, “请求宽恕比允许更好”(尝试操作并处理异常,如果有的话,而不是模糊代码的主流并通过彻底的初步检查产生开销)。我同意Gabriel的说法,一个简单的except几乎不是一个好主意(除非它所做的只是某种形式的记录,然后是raise让异常传播)。所以,假设您有一个元组,其中包含您期望并希望以相同方式处理的所有异常类型,例如:

expected_exceptions = KeyError, AttributeError, TypeError

始终使用except expected_exceptions:而不是except:

因此,如果不采用这种方法,可以根据您的需求略微重复一点:

try:
    foo1()
except expected_exceptions:
    try:
        if condition:
            foobetter()
        else:
            raise
    except expected_exceptions:
        handleError()

另一种方法是使用辅助函数来包装try / except逻辑:

def may_raise(expected_exceptions, somefunction, *a, **k):
  try:
    return False, somefunction(*a, **k)
  except expected_exceptions:
    return True, None

这样的帮助器在几种不同的情况下可能经常有用,所以在项目的“实用程序”模块中的某个地方有这样的东西是很常见的。现在,对于您的情况(没有参数,没有结果),您可以使用:

failed, _ = may_raise(expected_exceptions, foo1)
if failed and condition:
  failed, _ = may_raise(expected_exceptions, foobetter)
if failed:
  handleError()

我认为它更线性,因此更简单。这种通用方法的唯一问题是,诸如may_raise之类的辅助功能不会强迫您以某种方式处理异常,因此可能只是忘记这样做(只是比如使用返回代码而不是异常来表示错误,很容易错误地忽略那些返回值);所以,请谨慎使用......! - )

答案 1 :(得分:1)

使用一揽子例外通常不是一个好主意。你期待什么样的例外?它是KeyError,AttributeError,TypeError ......

一旦确定了您正在寻找的错误类型,您就可以使用hasattr()in运算符之类的内容或许多其他可在您处理之前测试您的情况的内容有例外。

这样你就可以清理你的逻辑流程,并为那些真正破坏的事物保存你的异常处理!

答案 2 :(得分:1)

有时没有很好的表达流程的方法,它只是复杂的。但是这里只能在一个地方调用foo(),并且只在一个地方进行错误处理:

if cacheExists:
    loadCache()
    dataComputed = False
else:
    calculateData()
    dataComputed = True

while True:
    try:
        foo()
        break
    except:
        if not dataComputed:
            calculateData()
            dataComputed = True
            continue 
        else:
            #the error handling code
            break

你可能不喜欢循环,YMMV ......

或者:

if cacheExists:
    loadCache()
    dataComputed = False
else:
    calculateData()
    dataComputed = True

done = False
while !done:
    try:
        foo()
        done = True
    except:
        if not dataComputed:
            calculateData()
            dataComputed = True
            continue 
        else:
            #the error handling code
            done = True

答案 3 :(得分:1)

我喜欢Alex Martelli提出的替代方法。

您如何使用函数列表作为may_raise的参数。这些函数将被执行直到成功!

这是代码

def foo(x):
    raise Exception("Arrrgh!")
    return 0

def foobetter(x):
    print "Hello", x
    return 1

def try_many(functions, expected_exceptions, *a, **k):
    ret = None
    for f in functions:
        try:
            ret = f(*a, **k)
        except expected_exceptions, e:
            print e
        else:
            break
    return ret

print try_many((foo, foobetter), Exception, "World")

结果是

Arrrgh!
Hello World
1

答案 4 :(得分:0)

有没有办法告诉你是否想在拨打电话前做foobetter()?如果你得到一个例外,那应该是因为发生了意想不到的事情(例外!)。不要使用异常进行流量控制。