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

发布于 2024-11-19 00:22:38 字数 1120 浏览 8 评论 0原文

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

一种方法是强制人们子类化“接口”类。对我来说,这感觉更像 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] 这样的愚蠢事情,我实际上会得到一个 TypeError 而不是 AttributeError (整数不是iterable)所以必须在某个地方进行一些类型检查——如果我想做同样的事情怎么办?

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

编辑

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

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

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

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. To me, this feels more like Java than Python and using issubclass in each method doesn't sound very tempting either.

My preferred way is to use the object in good faith, but this will raise some AttributeErrors. I could wrap each dangerous call in a try/except block. 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?

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 AttributeErrors are fine, the TypeErrors raised by native functions usually give more information about how to solve the errors. 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

束缚m 2024-11-26 00:22:38

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

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

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

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. 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__"). 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.

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!

烟燃烟灭 2024-11-26 00:22:38

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

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

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

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

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

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. Most of the time, an AttributeError is not only reasonable but expected when it comes to interface mismatches.

Occasionally, it may be appropriate to catch an AttributeError for one of two reasons. 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. 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 (i.e. something much worse than a stack trace).

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. 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.

城歌 2024-11-26 00:22:38

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

If you just want the unimplemented methods to do nothing, you can try something like this, rather than the multi-line try/except construction:

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

However, this isn't necessarily obvious as a function call, so maybe:

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.

Another option of course is to abstract the try/except into a separate function.

简单爱 2024-11-26 00:22:38

好问题,而且相当开放式。我相信典型的 Python 风格是不进行检查,无论是使用 isinstance 还是捕获单个异常。当然,使用 isinstance 是一种非常糟糕的风格,因为它破坏了鸭子类型的全部意义(尽管在基元上使用 isinstance 是可以的——一定要检查整数输入的 int 和 long,并检查字符串的 basestring (base str 和 unicode 的类)。如果您进行检查,则应该引发 TypeError 。

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

您看到 TypeErrors 的原因是原始代码会引发它,实际上是因为它执行了 isinstance,如果某些内容不可迭代,则 for 循环会被硬编码为引发 TypeError。

Good question, and quite open-ended. I believe typical Python style is not to check, either with isinstance or catching individual exceptions. 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.

Not checking is generally OK, as it typically raises either a TypeError or AttributeError anyway, which is what you want. (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. The for loop is hard-coded to raise a TypeError if something is not iterable.

薔薇婲 2024-11-26 00:22:38

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

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

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

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

编辑:kindall 正确地指出,如果您要检查这一点,您还应该检查 f 是否可调用。

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

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

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

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

您必须平衡这样一个事实:抛出这些漂亮的错误将使您的代码更难以阅读,从统计上来说,这意味着您的代码中存在更多错误。这是一个利弊权衡的问题。

如果您需要检查行为(例如 __real____contains__),请不要忘记使用 collections 中找到的 Python 抽象基类、io数字

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. 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.

In general, this is not 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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文