对MyPy的TypeVar绑定属性使用任意函数

2024-04-24 12:32:43 发布

您现在位置:Python中文网/ 问答频道 /正文

我最近深入研究了MyPy,从他们的文档中看到了以下示例

from typing import TypeVar, SupportsAbs

T = TypeVar('T', bound=SupportsAbs[float])

def largest_in_absolute_value(*xs: T) -> T:
    return max(xs, key=abs)  # Okay, because T is a subtype of SupportsAbs[float].

这说明可以使用mypy,这样传入的泛型必须支持abs函数才能通过静态类型检查器。在

但我不清楚这到底是怎么回事。例如,如果我可以指定一个类型必须支持的任何函数,或者指定一个类型必须介于两者之间的范围,那么我可以看到这个功能非常强大。在

我的问题如下:有没有一种方法可以使用bound来支持任何随机函数需求?例如,类型必须支持len函数?(我怀疑这是可能的)

特定变量类型的范围如何(即小于10个字符的字符串,或小于100的整数)?(我怀疑这不太可能)


Tags: 函数from文档import示例typing类型abs
1条回答
网友
1楼 · 发布于 2024-04-24 12:32:43

核心规则是:边界必须是某种合法的PEP-484类型。在

通常,这只会让您指定T最终必须由边界或边界的某个子类“填充”。例如:

class Parent: pass
class Child(Parent): pass

T = TypeVar('T', bound=Parent)

def foo(x: T) -> T: return x

# Legal; revealed type is 'Parent'
reveal_type(foo(Parent()))  

# Legal; revealed type is 'Child'
reveal_type(foo(Child()))

# Illegal, since ints are not subtypes of Parent
foo(3)

你可以做一些更有趣的事情,把你的边界设为Protocol。在

基本上,假设您有这样一个程序:

^{pr2}$

mypy将这两个类视为完全不相关的:它们可能碰巧共享一个具有相同签名的函数foo,但{}没有从{}继承,反之亦然,因此它们的相似性被视为巧合,因此被丢弃。在

我们可以通过将SupportsFoo转换为协议来改变这一点:

# If you're using Python 3.7 or below, pip-install typing_extensions
# and import Protocol from there
from typing import Protocol

class SupportsFoo(Protocol):
    def foo(self, x: int) -> str: ...

class Blah:
    def foo(self, x: int) -> str: ...

# This succeeds!
x: SupportsFoo = Blah()

现在,这成功了!Mypy知道Blah具有与SupportsFoo完全相同的签名的方法,因此将其视为前者的子类型。在


这正是SupportsAbs所发生的事情,您可以检查Typeshed上的definition of that type for yourself,这是标准库的类型提示库。(每个mypy版本中都会有Typeshed的副本):

@runtime_checkable
class SupportsAbs(Protocol[_T_co]):
    @abstractmethod
    def __abs__(self) -> _T_co: ...

是的,正如您所要求的,您还可以创建一个协议来坚持输入类型使用typing.Sized实现{},其定义如下:

@runtime_checkable
class Sized(Protocol, metaclass=ABCMeta):
    @abstractmethod
    def __len__(self) -> int: ...

是的,你的直觉是没有一种干净的方法来创建断言诸如“this string must be 10 characters or less”或“this must an int less than 100”这样的类型是正确的。在

我们可以通过使用一种称为Literal types的不相关机制来支持这一点,方法如下:

# As before, import from typing_extensions for Python 3.7 or less
from typing import Literal

BetweenZeroAndOneHundred = Literal[
    0, 1, 2, 3, 4, 5,
    # ...snip...
    96, 97, 98, 99, 100,
]

但这是相当老套的,实际上价值非常有限。在

更好的解决方案是通常只在运行时执行自定义检查并使用NewType

from typing import NewType

LessThanOneHundred = NewType('LessThanOneHundred', int)

def to_less_than_one_hundred(value: int) -> LessThanOneHundred:
    assert value < 100
    return LessThanOneHundred(value)

这并不是一个完美的解决方案,因为它要求您在运行时执行检查/要求您确保在完成运行时检查后只“实例化”新类型,但它是一种实际可用的方法,可以以类型检查器可以理解的形式对任意运行时检查的结果进行编码。在

相关问题 更多 >