27

我通常希望我的代码尽可能通用。我目前正在编写一个简单的库,这次能够在我的库中使用不同的类型感觉特别重要。

一种方法是强迫人们继承一个“接口”类。对我来说,这感觉更像是 Java 而不是 Python,并且issubclass在每种方法中使用听起来也不是很诱人。

我的首选方式是善意使用该对象,但这会引发一些AttributeErrors. 我可以将每个危险的调用包装在一个 try/except 块中。这也似乎有点麻烦:

def foo(obj):
    ...
    # it should be able to sleep
    try:
        obj.sleep()
    except AttributeError:
        # handle error
    ...
    # it should be able to wag it's tail
    try:
        obj.wag_tail()
    except AttributeError:
        # handle this error as well

我是否应该跳过错误处理并期望人们只使用具有所需方法的对象?如果我做了一些愚蠢的事情,比如[x**2 for x in 1234]我实际上得到的是 aTypeError而不是 a AttributeError(整数不可迭代),那么某处必须进行一些类型检查——如果我想做同样的事情怎么办?

这个问题有点开放式,但是以干净的方式处理上述问题的最佳方法是什么?是否有任何既定的最佳实践?例如,上面的可迭代“类型检查”是如何实现的?

编辑

虽然AttributeErrors 很好,但TypeErrors本机函数引发的通常会提供有关如何解决错误的更多信息。以此为例:

>>> ['a', 1].sort()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

我希望我的图书馆尽可能有用。

4

5 回答 5

18

我不是 python 专业人士,但我相信,除非您可以在参数未实现给定方法时尝试替代方法,否则您不应该阻止抛出异常。让调用者处理这些异常。这样,您将向开发人员隐藏问题。

正如我在Clean Code中所读到的,如果您想在集合中搜索一个项目,请不要使用ìssubclass(of a list) 测试您的参数,而是更喜欢调用getattr(l, "__contains__"). 这将使正在使用您的代码的人有机会传递一个不是列表但__contains__定义了方法的参数,并且事情应该同样有效。

所以,我认为你应该以抽象的、通用的方式编码,尽可能少地施加限制。为此,您必须做出尽可能少的假设。然而,当你遇到一些你无法处理的事情时,抛出一个异常,让程序员知道他犯了什么错误!

于 2011-07-05T23:41:39.260 回答
12

如果你的代码需要一个特定的接口,而用户传递了一个没有该接口的对象,那么十有八九是不适合捕获异常的。大多数时候,AttributeError当涉及到接口不匹配时,an 不仅是合理的,而且是预期的。

AttributeError有时,出于以下两个原因之一,捕获一个可能是合适的。要么你希望接口的某些方面是可选的,要么你想抛出一个更具体的异常,也许是一个特定于包的异常子类。当然,如果你没有诚实地处理错误和任何后果,你永远不应该阻止抛出异常。

所以在我看来,这个问题的答案必须是特定于问题和领域的。从根本上说,使用Cow对象而不是Duck对象是否应该起作用的问题。如果是这样,并且您可以处理任何必要的界面伪造,那很好。另一方面,没有理由明确检查是否有人向您传递了一个Frog对象,除非这会导致灾难性的失败(即比堆栈跟踪更糟糕的事情)。

也就是说,记录你的界面总是一个好主意——这就是文档字符串(以及其他东西)的用途。当您考虑它时,在大多数情况下抛出一般错误并在文档字符串中告诉用户正确的方法比尝试预测用户可能犯的每个可能的错误并创建自定义错误消息要有效得多。

最后一个警告——你可能在这里考虑UI——我认为这是另一个故事。最好检查最终用户给您的输入以确保它不是恶意的或严重的格式错误,并提供有用的反馈而不是堆栈跟踪。但是对于库或类似的东西,你真的必须相信使用你的代码的程序员会聪明而尊重地使用它,并理解 Python 生成的错误。

于 2011-07-06T00:16:10.267 回答
7

如果你只是想让未实现的方法什么都不做,你可以尝试这样的事情,而不是多行try/except构造:

getattr(obj, "sleep", lambda: None)()

但是,这作为函数调用并不一定很明显,所以也许:

hasattr(obj, "sleep") and obj.sleep()

或者,如果您想在调用实际上可以调用的东西之前更加确定:

hasattr(obj, "sleep") and callable(obj.sleep) and obj.sleep()

这种“先看再跳”模式通常不是在 Python 中执行此操作的首选方式,但它非常易读并且适合一行。

当然,另一种选择是将其抽象try/except为一个单独的函数。

于 2011-07-05T23:44:03.913 回答
6

好问题,而且非常开放。我相信典型的 Python 风格不是检查,无论是使用 isinstance 还是捕获个别异常。Cerainly,使用 isinstance 是一种非常糟糕的风格,因为它破坏了鸭子类型的全部意义(尽管在原语上使用 isinstance 可以 - 确保检查整数输入的 int 和 long ,并检查字符串的 basestring(base str 和 unicode 的类)。如果你检查,你应该引发一个 TypeError。

不检查通常是可以的,因为它通常会引发 TypeError 或 AttributeError ,这就是你想要的。(尽管它可以延迟那些使客户端代码难以调试的错误)。

您看到 TypeErrors 的原因是原始代码有效地引发了它,因为它执行了一个 isinstance。如果某些东西不可迭代,for 循环被硬编码以引发 TypeError。

于 2011-07-05T23:33:36.290 回答
3

首先,您问题中的代码并不理想:

try:
    obj.wag_tail()
except AttributeError:
    ...

您不知道AttributeError是来自查找wag_tail还是发生在函数内部。你想要做的是:

try:
    f = getattr(obj, 'wag_tail')
except AttributeError:
    ...
finally:
    f()

编辑:正确地指出,如果你要检查这个,你还应该检查它f是可调用的。

一般来说,这不是 Pythonic。只需调用并让异常过滤掉,堆栈跟踪就足以解决问题。我认为您应该问自己,您重新抛出的异常是否足以证明所有这些错误检查代码的合理性。

对列表进行排序就是一个很好的例子。

  • 列表排序很常见,
  • 其中很大一部分发生了传递不可排序的类型,并且
  • 在这种情况下抛出 AttributeError 非常令人困惑。

如果这三个标准适用于您的问题(尤其是第三个),我同意构建漂亮的异常抛出器。

你必须权衡这样一个事实,即抛出这些漂亮的错误会使你的代码更难阅读,这在统计上意味着你的代码中有更多的错误。这是一个权衡利弊的问题。

如果您需要检查行为(如__real____contains__),请不要忘记使用在 、 和 中找到的 Python 抽象collectionsionumbers

于 2011-07-06T07:24:51.753 回答