简体   繁体   English

如何处理 Python 中的“鸭子打字”?

[英]How to handle “duck typing” in Python?

I usually want to keep my code as generic as possible.我通常希望我的代码尽可能通用。 I'm currently writing a simple library and being able to use different types with my library feels extra important this time.我目前正在编写一个简单的库,这次能够在我的库中使用不同的类型感觉特别重要。

One way to go is to force people to subclass an "interface" class. go 的一种方法是强制人们对“接口”class 进行子类化。 To me, this feels more like Java than Python and using issubclass in each method doesn't sound very tempting either.对我来说,这感觉更像是 Java 而不是 Python 并且在每种方法中使用issubclass听起来也不是很诱人。

My preferred way is to use the object in good faith, but this will raise some AttributeErrors .我的首选方法是善意使用 object,但这会引发一些AttributeErrors I could wrap each dangerous call in a try/except block.我可以将每个危险的调用包装在一个 try/except 块中。 This, too, seems kind of cumbersome:这也似乎有点麻烦:

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

Should I just skip the error handling and expect people to only use objects with the required methods?我是否应该跳过错误处理并期望人们只使用具有所需方法的对象? If I do something stupid like [x**2 for x in 1234] I actually get a TypeError and not a AttributeError (ints are not iterable) so there must be some type checking going on somewhere -- what if I want to do the same?如果我做了一些愚蠢的事情,比如[x**2 for x in 1234]我实际上得到一个TypeError而不是AttributeError (整数不可迭代)所以一定有一些类型检查在某处进行 - 如果我想做相同的?

This question will be kind of open ended, but what is the best way to handle the above problem in a clean way?这个问题有点开放式,但是以干净的方式处理上述问题的最佳方法是什么? Are there any established best practices?是否有任何既定的最佳实践? How is the iterable "type checking" above, for example, implemented?例如,上面的可迭代“类型检查”是如何实现的?

Edit编辑

While AttributeError s are fine, the TypeErrors raised by native functions usually give more information about how to solve the errors.虽然AttributeError很好,但本机函数引发的TypeErrors通常会提供有关如何解决错误的更多信息。 Take this for example:以此为例:

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

I'd like my library to be as helpful as possible.我希望我的图书馆尽可能有用。

I'm not a python pro but I believe that unless you can try an alternative for when the parameter doesn't implement a given method, you shoudn't prevent exceptions from being thrown.我不是 python 专业人士,但我相信除非您可以在参数未实现给定方法时尝试替代方法,否则您不应阻止引发异常。 Let the caller handle these exceptions.让调用者处理这些异常。 This way, you would be hidding problems from the developers.这样,您将向开发人员隐藏问题。

As I have read in Clean Code , if you want to search for an item in a collection, don't test your parameters with ìssubclass (of a list) but prefer to call getattr(l, "__contains__") .正如我在Clean Code中所读到的,如果您想在集合中搜索一个项目,请不要使用ìssubclass (列表的)测试您的参数,而是更愿意调用getattr(l, "__contains__") This will give someone who is using your code a chance to pass a parameter that isn't a list but which has a __contains__ method defined and things should work equally well.这将使正在使用您的代码的人有机会传递一个不是列表但定义了__contains__方法的参数,并且事情应该同样有效。

So, I think that you should code in an abstract , generic way, imposing as few restrictions as you can.所以,我认为你应该以抽象的、通用的方式编码,尽可能少地施加限制 For that, you'll have to make the fewest assumptions possible.为此,您必须做出尽可能少的假设。 However, when you face something that you can't handle, raise an exception and let the programmer know what mistake he made!然而,当你遇到一些你无法处理的事情时,抛出一个异常,让程序员知道他犯了什么错误!

If your code requires a particular interface, and the user passes an object without that interface, then nine times out of ten, it's inappropriate to catch the exception.如果你的代码需要一个特定的接口,而用户传递了一个没有该接口的 object,那么十分之九,捕获异常是不合适的。 Most of the time, an AttributeError is not only reasonable but expected when it comes to interface mismatches.大多数时候,当涉及到接口不匹配时, AttributeError不仅是合理的,而且是意料之中的。

Occasionally, it may be appropriate to catch an AttributeError for one of two reasons.有时,出于两个原因之一捕获AttributeError可能是合适的。 Either you want some aspect of the interface to be optional , or you want to throw a more specific exception, perhaps a package-specific exception subclass.要么你希望接口的某些方面是可选的,要么你想抛出一个更具体的异常,也许是一个特定于包的异常子类。 Certainly you should never prevent an exception from being thrown if you haven't honestly handled the error and any aftermath.当然,如果你没有诚实地处理错误和任何后果,你永远不应该阻止抛出异常。

So it seems to me that the answer to this question must be problem- and domain- specific.所以在我看来,这个问题的答案必须是特定于问题和领域的。 It's fundamentally a question of whether using a Cow object instead of a Duck object ought to work.从根本上说,使用Cow object 而不是Duck object 是否应该起作用的问题。 If so, and you handle any necessary interface fudging, then that's fine.如果是这样,并且您可以处理任何必要的界面伪造,那很好。 On the other hand, there's no reason to explicitly check whether someone has passed you a Frog object, unless that will cause a disastrous failure (ie something much worse than a stack trace).另一方面,没有理由明确检查是否有人向您传递了Frog object,除非这会导致灾难性故障(即比堆栈跟踪更糟糕的事情)。

That said, it's always a good idea to document your interface -- that's what docstrings (among other things) are for.也就是说,记录你的界面总是一个好主意——这就是文档字符串(以及其他东西)的用途。 When you think about it, it's much more efficient to throw a general error for most cases and tell users the right way to do it in the docstring, than to try to foresee every possible error a user might make and create a custom error message.当您考虑它时,在大多数情况下抛出一般错误并在文档字符串中告诉用户正确的方法比尝试预见用户可能犯的每个可能的错误并创建自定义错误消息要有效得多。

A final caveat -- it's possible that you're thinking about UI here -- I think that's another story.最后一个警告——你可能在这里考虑UI——我认为这是另一个故事。 It's good to check the input that an end user gives you to make sure it isn't malicious or horribly malformed, and provide useful feedback instead of a stack trace.最好检查最终用户给您的输入以确保它不是恶意的或严重的格式错误,并提供有用的反馈而不是堆栈跟踪。 But for libraries or things like that, you really have to trust the programmer using your code to use it intelligently and respectfully, and to understand the errors that Python generates.但是对于库或类似的东西,你真的必须相信使用你的代码的程序员会聪明而尊重地使用它,并理解 Python 生成的错误。

If you just want the unimplemented methods to do nothing, you can try something like this, rather than the multi-line try/except construction:如果你只是想让未实现的方法什么都不做,你可以尝试这样的事情,而不是多行try/except构造:

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

However, this isn't necessarily obvious as a function call, so maybe:但是,作为 function 调用,这不一定很明显,所以也许:

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

or if you want to be a little more sure before calling something that it can in fact be called:或者,如果您想在调用实际上可以调用的东西之前更加确定:

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

This "look-before-you-leap" pattern is generally not the preferred way to do it in Python, but it is perfectly readable and fits on a single line.这种“先看后跳”模式在 Python 中通常不是首选的方法,但它非常易读并且适合一行。

Another option of course is to abstract the try/except into a separate function.当然,另一种选择是将try/except抽象为单独的 function。

Good question, and quite open-ended.好问题,而且非常开放。 I believe typical Python style is not to check, either with isinstance or catching individual exceptions.我相信典型的 Python 风格不是检查,无论是使用 isinstance 还是捕获个别异常。 Cerainly, using isinstance is quite bad style, as it defeats the whole point of duck typing (though using isinstance on primitives can be OK -- be sure to check for both int and long for integer inputs, and check for basestring for strings (base class of str and unicode). If you do check, you hould raise a TypeError. Cerainly,使用 isinstance 是一种非常糟糕的风格,因为它破坏了鸭子类型的全部意义(尽管在原语上使用 isinstance 可以 - 确保检查 integer 输入的 int 和 long ,并检查字符串的 basestring(base class 的 str 和 unicode)。如果你检查,你应该引发 TypeError。

Not checking is generally OK, as it typically raises either a TypeError or AttributeError anyway, which is what you want.不检查通常是可以的,因为它通常会引发 TypeError 或 AttributeError ,这就是你想要的。 (Though it can delay those errors making client code hard to debug). (尽管它可以延迟那些使客户端代码难以调试的错误)。

The reason you see TypeErrors is that primitive code raises it, effectively because it does an isinstance.您看到 TypeErrors 的原因是原始代码有效地引发了它,因为它执行了一个 isinstance。 The for loop is hard-coded to raise a TypeError if something is not iterable.如果某些东西不可迭代,for 循环被硬编码以引发 TypeError。

First of all, the code in your question is not ideal:首先,您问题中的代码并不理想:

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

You don't know whether the AttributeError is from the lookup of wag_tail or whether it happened inside the function.您不知道AttributeError是来自wag_tail的查找还是发生在 function 内部。 What you are trying to do is:你想要做的是:

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

Edit: kindall rightly points out that if you are going to check this, you should also check that f is callable.编辑:正确地指出,如果你要检查这个,你还应该检查f是可调用的。

In general, this is not Pythonic.一般来说,这不是 Pythonic。 Just call and let the exception filter down, and the stack trace is informative enough to fix the problem.只需调用并让异常过滤掉,堆栈跟踪就足以解决问题。 I think you should ask yourself whether your rethrown exceptions are useful enough to justify all of this error-checking code.我认为您应该问自己,您重新抛出的异常是否足以证明所有这些错误检查代码的合理性。

The case of sorting a list is a great example.对列表进行排序就是一个很好的例子。

  • List sorting is very common,列表排序很常见,
  • passing unorderable types happens for a significant proportion of those, and其中很大一部分发生了传递不可排序的类型,并且
  • throwing AttributeError in that case is very confusing.在这种情况下抛出 AttributeError 非常令人困惑。

If those three criteria apply to your problem (especially the third), I agree with building pretty exception rethrower.如果这三个标准适用于您的问题(尤其是第三个),我同意构建漂亮的异常抛出器。

You have to balance with the fact that throwing these pretty errors is going to make your code harder to read, which statistically means more bugs in your code.你必须权衡这样一个事实,即抛出这些漂亮的错误会使你的代码更难阅读,这在统计上意味着你的代码中有更多的错误。 It's a question of balancing the pros and the cons.这是一个权衡利弊的问题。

If you ever need to check for behaviours (like __real__ and __contains__ ), don't forget to use the Python abstract base classes found in collections , io , and numbers .如果您需要检查行为(如__real____contains__ ),请不要忘记使用collectionsionumbers中的 Python 抽象基类,

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM