Python - 打字 - 可下标类型的联合

bon*_*ris 6 python numpy type-hinting python-3.x mypy

我想创建一个Array类型,它应该是可下标的并且是typing.Listnumpy.ndarray类型的联合。我知道numpy没有存根,但是那些 numpy 存根(由Machinalis 提供)应该可以正常工作,因为它们是可订阅的。

这是预期的行为:

def foo(bar: Array[int])->None:
    pass

foo([1,2,3])          # No typing error
foo(numpy.arange(4))  # No typing error
foo((1,2,3))          # Error: Expected Array[int], got Tuple[int]
foo([1.,2.,3.])       # Error: Expected Array[int], got Array[float]
Run Code Online (Sandbox Code Playgroud)

我已经尝试了一些东西,但没有一个像预期的那样工作。

你会如何在 Python 3.7 中做到这一点?

我也会接受某种鸭子类型的解决方案,即使它不满足元组错误。重点是创建可下标类型的可下标联合。

谢谢。

我最好的尝试:(评论中的 mypy 错误)

class _meta_getitem(type):
    def __getitem__(cls, x):
        return cls.__getitem__(cls, x)

class Array(metaclass=_meta_getitem):

    def __getitem__(self, element_type: type) -> type:
        array_type = typing.Union[List[element_type],  # error: Invalid type "element_type"
                                  numpy.ndarray[element_type]]
        return typing.NewType("Array[{}]".format(element_type.__name__), 
                              array_type)  # The type alias to Union is invalid in runtime context

if __name__ == "__name__":
    x: Array[int] = numpy.arange(4) # "Array" expects no type arguments, but 1 given
Run Code Online (Sandbox Code Playgroud)

Mic*_*x2a 7

创建一个类型别名Union[List[T], Array[T]]应该可以工作:

from typing import TypeVar, Union, List

T = TypeVar('T')
Array = Union[List[T], numpy.ndarray[T]]

def foo(bar: Array[int]) -> None: pass
Run Code Online (Sandbox Code Playgroud)

有关此技术的更多信息,请参阅有关泛型类型别名的 mypy 文档。

此代码可能会在运行时失败,因为在运行时numpy.ndarray实际上不可下标,仅在类型提示世界中。您可以通过将自定义类型提示隐藏在typing.TYPE_CHECKING保护后面来解决此问题,该提示在运行时始终为 false,在类型检查时始终为 true。

您可以在 Python 3.7+ 中相对干净地执行此操作:

from __future__ import annotations
from typing import TypeVar, Union, List, TYPE_CHECKING

if TYPE_CHECKING:
    T = TypeVar('T')
    Array = Union[List[T], numpy.ndarray[T]]

def foo(bar: Array[int]) -> None: pass
Run Code Online (Sandbox Code Playgroud)

Array[int]但是,对于旧版本的 Python 3,您必须将其包裹在字符串中:

from typing import TypeVar, Union, List, TYPE_CHECKING

if TYPE_CHECKING:
    T = TypeVar('T')
    Array = Union[List[T], numpy.ndarray[T]]

def foo(bar: "Array[int]") -> None: pass
Run Code Online (Sandbox Code Playgroud)

请注意,尝试Array通过在运行时将其他几个类型提示组合在一起来构建自己的类型提示不太可能奏效:像 mypy 这样的静态分析工具通过实际分析代码而不运行它来工作:它实际上不会尝试评估您内部的任何内容自定义Array类。

更一般地说,尝试在运行时“使用”类型提示往往充满危险:它们实际上只是用作类型提示。

最后,你似乎误解了NewType它的用途。我建议阅读相关文档以获取更多信息