如何在Python中处理“鸭子类型”?

2024-04-20 07:14:24 发布

您现在位置:Python中文网/ 问答频道 /正文

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

一种方法是强制人们对“接口”类进行子类化。对我来说,这感觉更像Java而不是Python,在每个方法中使用issubclass听起来也不太诱人。

我更喜欢的方法是真诚地使用对象,但这会增加一些AttributeErrors。我可以把每一个危险的电话都包在一个试听区里。这也有点麻烦:

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]这样愚蠢的事情,我实际上得到了一个TypeError,而不是一个AttributeError(int是不可接受的),所以一定有一些类型检查在某个地方进行——如果我想做同样的事情呢?

这个问题是一个开放式的问题,但是什么是最好的方法来干净地处理上述问题呢?是否有任何既定的最佳实践?例如,上面的iterable“类型检查”是如何实现的?

编辑

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

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

我希望我的图书馆能尽可能地提供帮助。


Tags: to对象方法obj类型ableitsleep
3条回答

如果您只想让未实现的方法不做任何事情,那么可以尝试这样的方法,而不是多行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抽象为一个单独的函数。

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

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

所以在我看来,这个问题的答案必须是特定于问题和领域的。从根本上讲,问题是使用Cow对象而不是Duck对象是否应该工作。如果是的话,你可以处理任何必要的接口伪造,那就没问题了。另一方面,没有理由显式检查是否有人向您传递了一个Frog对象,除非这将导致灾难性的失败(即比堆栈跟踪更糟糕的情况)。

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

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

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

正如我在Clean Code中读到的,如果要在集合中搜索项目,不要用ìssubclass(列表)来测试参数,而应该调用getattr(l, "__contains__")。这将使使用您的代码的人有机会传递一个参数,该参数不是一个列表,而是定义了一个__contains__方法,并且工作应该同样良好。

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

相关问题 更多 >