3

我有一些测试用例。测试用例依赖于需要时间计算的数据。为了加快测试速度,我缓存了数据,这样就不必重新计算了。

我现在有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

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

4

5 回答 5

4

我不同意现有答案中的关键建议,这基本上归结为在 Python 中处理异常,就像在 C++ 或 Java 中一样——这不是 Python 中的首选风格,通常是“它更好”的好旧想法请求宽恕而不是许可”(尝试操作并处理异常(如果有),而不是通过彻底的初步检查来掩盖代码的主要流程并产生开销)。我确实同意 Gabriel 的观点,即裸露except几乎不是一个好主意(除非它所做的只是某种形式的日志记录,然后是raise让异常传播的 a)。因此,假设您有一个包含您确实期望并希望以相同方式处理的所有异常类型的元组,例如:

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不会强迫您以某种方式处理异常,因此您可能只是忘记这样做(就像使用返回码而不是异常一样,指示错误,容易导致那些返回值被错误地忽略);所以,请谨慎使用它...!-)

于 2009-08-27T21:26:26.757 回答
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
于 2009-08-28T01:00:47.687 回答
1

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

您如何看待使用函数列表作为 may_raise 的参数。函数将一直执行到一个成功为止!

这是代码

定义 foo(x):
    引发异常(“啊!”)
    返回 0

def foobetter(x):
    打印“你好”,x
    返回 1

def try_many(functions, expected_exceptions, *a, **k):
    ret = 无
    对于 f in 函数:
        尝试:
            ret = f(*a, **k)
        除了expected_exceptions,e:
            打印 e
        别的:
            休息
    返回 ret

打印 try_many((foo, foobetter), Exception, "World")

结果是

啊!
你好世界
1
于 2009-08-28T12:40:01.260 回答
1

使用一揽子异常通常不是一个好主意。你在那里期待什么样的异常?是KeyError, AttributeError, TypeError...

一旦您确定了您要查找的错误类型,您就可以使用类似hasattr()in运算符之类的东西或许多其他东西来测试您的情况,然后再处理异常。

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

于 2009-08-27T20:29:44.733 回答
0

有没有办法在打电话之前判断你是否想做 foobetter() ?如果您遇到异常,那应该是因为发生了意外(异常!)。不要使用异常进行流量控制。

于 2009-08-27T20:25:24.397 回答