【问题标题】:Python type hint for classes that support __getitem__支持 __getitem__ 的类的 Python 类型提示
【发布时间】:2019-03-12 17:54:35
【问题描述】:

我想向一个函数添加类型提示,该函数将接受具有__getitem__ 方法的任何对象。例如,在

def my_function(hasitems, locator):
    hasitems[locator]

我不想将 hasitems 限制为特定类型,例如 listdict。只要它支持__getitem__,它就是my_function 的合适参数。如何在没有不必要限制的情况下注释其类型?

编辑:显然 PyCharm 可以在许多常见情况下推断出适当的提示,但在我的实际用例中却不能。我无法发布代码,因为它是用于工作的,而且我无法找到 PyCharm 失败的非专有最小示例。在任何情况下,原始问题都没有引用 PyCharm,它仍然是类型提示的有效用例。

【问题讨论】:

  • 用那个单一的抽象方法创建一个abc
  • 请问这样的提示有什么用? IDE 已经根据使用情况自动扣除提示。您是否将这些提示用于其他目的?
  • 然后升级。对我来说,调用 my_function(5, 10) 时 PyCharm 产生:> 类型 'int' 没有预期的属性 'getitem' 更少... (Ctrl+F1) 检查信息:此检查检测函数中的类型错误调用表达式。由于动态调度和鸭子类型,这在有限但有用的情况下是可能的。函数参数的类型可以在文档字符串或 Python 3 函数注释中指定。
  • 我建议您将实际代码放在问题中。这样我们可以更有帮助。
  • 您使用的是哪个 Python 版本? 3.7 引入了协议,应该可以解决这个问题:python.org/dev/peps/pep-0544

标签: python type-hinting python-typing


【解决方案1】:

如果您愿意为typingtyping-extensions 安装一个不太正式的扩展,您可以使用Protocol,它应该是PEP-0544 的实现:

from typing_extensions import Protocol
from typing import Any

class GetItem(Protocol):
    def __getitem__(self: 'Getitem', key: Any) -> Any: pass

class BadGetItem:
    def __getitem__(self, a: int, b: int) -> Any: pass

def do_thing(arg: GetItem):
    pass

do_thing(dict())  # OK
do_thing(BadGetItem())  # Fails with explanation of correct signature
do_thing(1)  # Fails

【讨论】:

  • 这是迄今为止最好的解决方案,但它忽略了函数的参数。它不验证函数的参数数量和参数类型。
  • @LiranFunaro 你为什么这么说?当我在上面的示例上运行 mypy 时(我刚刚添加了一个测试用例),由于签名不兼容,它会导致 BadGetItem 对象失败。
  • 我的回应是基于 PyCharm 在参数不兼容时不会发出警告这一事实。此外,isinstance() 针对相同的情况返回 True
  • 您应该将Any 替换为两个单独的TypeVar 实例。我在想K = TypeVar('K')V = TypeVar('V'),然后在class GetItem(Protocol) 中签名将是:__getitem__(self, key: K) -> V。当然,在类型提示中实现 __class_getitem__ 以支持 GetItem[SomeKeyType, SomeValueType]
  • 当然不是手动实现__class_getitem__,从Generic[K, V]继承似乎是更合适的方法。我还没有测试它是否真的通过了各种类型的短绒,但我会尝试类似class GetItem(Protocol, Generic[K, V])
【解决方案2】:

听起来您实际上想定义自己的abstract base class (abc)

按照上面的文档,您可以定义一个自定义 abc,它只指示 __getitem__ 的存在,但让我们使用预定义的 abc 作为示例。 Mapping abc 由__getitem__ 和其他一些魔术方法组成。 isinstance中可以使用abcs,也可以直接作为类型注解使用:

def foo(bar: Mapping):
    pass

或者,使用extended type hinting support of ABCs 做一些你已经在其他答案中看到的花哨的事情:

def foo(bar: Mapping[Any, Any]):
    pass

【讨论】:

  • Mapping 在这里不合适,会过于严格。
  • 这就是为什么我说我只用它作为例子,OP可以定义自己的ABC,只包含__getitem__
  • 如果这确实是最好的方法,我对typing 模块感到失望。我的用例一定很常见。
  • 您总是可以直接输入hasattr(x, '__getitem__') 或在 try-except 中进行查找。 python 的类型提示看起来和工作起来确实很笨重 imo
  • 谢谢,但我对保护输入类型不感兴趣。我只想要一个适用于 PyCharm 的类型提示。
【解决方案3】:

这适用于 dict 和 list,但不适用于任何泛型:

from typing import Any, Mapping, Sequence, Union

def my_function(hasitems: Union[Mapping, Sequence], locator: Any) -> Any:
    return hasitems[locator]

【讨论】:

  • 嗯,不完全是。 Mapping 不仅仅是__getitem__。另请注意,list 对象支持__getitem__,但它们不是Mapping
  • typing.Sequence 是另一个“定义”__getitem__ 的 ABC,尽管这可能比 OP 想要的更具体。 (因为它还要求 __len____iter__ 等)
  • @LiranFunaro 谢谢,但我特别要求提供不限于dictlist 的解决方案。
  • @chepner 正确,强制 __len____iter__ 不行。
  • 我认为您所要求的内容几乎宽泛了。当然,支持__getitem__ 但没有其他可以 存在的类,但有任何理由支持 它们吗?除非你有一个特定的类 Union[Mapping,Sequence] 覆盖,否则我会一直使用它,直到它真正出现问题为止。
猜你喜欢
  • 2018-04-21
  • 2014-07-15
  • 2013-07-15
  • 1970-01-01
  • 2021-07-15
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2019-10-11
相关资源
最近更新 更多