如何干净地编写 __getitem__?

新手上路,请多包涵

在 Python 中,当实现一个序列类型时,我经常(相对而言)发现自己写的代码是这样的:

 class FooSequence(collections.abc.Sequence):
    # Snip other methods

    def __getitem__(self, key):
        if isinstance(key, int):
            # Get a single item
        elif isinstance(key, slice):
            # Get a whole slice
        else:
            raise TypeError('Index must be int, not {}'.format(type(key).__name__))

该代码使用 isinstance() 明确检查其参数的类型。这在 Python 社区中 被视为一种反模式。我该如何避免呢?

  • 我不能使用 functools.singledispatch ,因为它 故意 与方法不兼容(它将尝试在 self 上调度,这完全没用,因为我们已经在 self 通过 OOP 多态性)。它适用于 @staticmethod ,但是如果我需要从 self 中取出东西怎么办?
  • 转换为 int() 然后捕获 TypeError ,检查切片,并可能重新加注仍然很难看,尽管可能稍微不那么难看。
  • 将整数转换为单元素切片并使用相同的代码处理这两种情况可能更清晰,但这有其自身的问题(返回 0[0] ?)。

原文由 Kevin 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 459
2 个回答

尽管看起来很奇怪,但我怀疑您拥有它的方式是处理事情的最佳方式。模式通常存在以涵盖常见的用例,但这并不意味着当遵循它们会使生活变得更加困难时,它们应该被视为福音。 PEP 443 拒绝显式类型检查的主要原因是它“脆弱且无法扩展”。但是,这主要适用于随时采用多种不同类型的自定义函数。来自 __getitem__ 上的 Python 文档

对于序列类型,接受的键应该是整数和切片对象。请注意,负索引的特殊解释(如果类希望模拟序列类型)取决于 getitem() 方法。如果键的类型不合适,可能会引发 TypeError;如果值超出序列的索引集(在对负值进行任何特殊解释之后),则应引发 IndexError 。对于映射类型,如果缺少键(不在容器中),则应引发 KeyError。

Python 文档明确说明了应该接受的两种类型,以及如果提供的项目不属于这两种类型,该怎么办。鉴于类型是由文档本身提供的,它不太可能改变(这样做会破坏比你自己的更多的实现),所以不值得费心去针对 Python 本身可能发生变化的代码进行编码。

如果您打算避免显式类型检查,我会向您指出 这个 SO answer 。它包含一个 @methdispatch 装饰器(不是我的名字,但我会用它滚动)的简洁实现,它允许 @singledispatch 通过强制检查 args[1] 来使用方法 --- (arg) 而不是 args[0] (self)。使用它应该允许您将自定义单一调度与您的 __getitem__ 方法一起使用。

您是否认为这些“pythonic”中的任何一个取决于您,但请记住,虽然 Python 之禅指出“特殊情况不足以打破规则”,但它立即指出“实用性胜过纯粹性” .在这种情况下,仅检查文档明确指出的两种类型是唯一的东西 __getitem__ 应该支持对我来说似乎是实用的方法。

原文由 Seven Deadly Sins 发布,翻译遵循 CC BY-SA 4.0 许可协议

反模式用于代码进行显式类型检查,这意味着使用 type() 函数。为什么?因为那时目标类型的子类将不再起作用。 For instance, __getitem__ can use an int , but using type() to check for it means an int -subclass, which would work,只会失败,因为 type() 不返回 int

当需要进行类型检查时, isinstance 是合适的方法,因为它不排除子类。

编写 __dunder__ 方法时,类型检查是必要和预期的——使用 isinstance()

换句话说,您的代码完全是 Pythonic,它唯一的问题是错误消息(它没有提到 slice s)。

原文由 Ethan Furman 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题