将任意函数与 MyPy 的 TypeVar 绑定属性一起使用
Use arbitrary function with MyPy's TypeVar bound attribute
我最近一直在深入研究 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函数才能通过静态类型检查器。
但我不清楚这是如何工作的。例如,如果我可以指定一个类型必须支持的任何函数,或者该类型必须介于两者之间的范围,我可以看到这非常强大。
我的问题如下:有没有办法使用绑定来支持任何随机函数要求?例如,该类型必须支持 len
功能? (我怀疑这是可能的)
特定变量类型的范围如何(即小于 10 个字符的字符串,或小于 100 的整数)? (我怀疑这是不太可能的)
核心规则是:绑定需要是某种合法的 PEP-484 类型。
通常,所有这一切都是让您指定 T 最终必须 "filled" 由绑定或绑定的某个子类。例如:
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.
来做一些更有趣的事情
基本上,假设您有这样一个程序:
class SupportsFoo:
def foo(self, x: int) -> str: ...
class Blah:
def foo(self, x: int) -> str: ...
# The two types are not related, so this fails with a
# 'Incompatible types in assignment' error -- the RHS needs
# to be a subtype of the declared type of the LHS.
x: SupportsFoo = Blah()
这两个 类 被 mypy 视为完全无关:它们可能碰巧共享一个具有相同签名的函数 foo
,但 Blah
不继承自SupportsFoo
反之亦然,因此它们的相似性被视为巧合,因此被丢弃。
我们可以通过将 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。 (Typeshed 的副本被烘焙到每个 mypy 版本中):
@runtime_checkable
class SupportsAbs(Protocol[_T_co]):
@abstractmethod
def __abs__(self) -> _T_co: ...
是的,正如您所要求的,您还可以创建一个协议来坚持输入类型使用 typing.Sized
实现 __len__
,其定义如下:
@runtime_checkable
class Sized(Protocol, metaclass=ABCMeta):
@abstractmethod
def __len__(self) -> int: ...
是的,您的直觉是没有一种干净的方法来创建断言 "this string must be 10 characters or less" 或 "this must be an int less then 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)
这不是一个完美的解决方案,因为它要求您在 runtime/requires 处进行检查,以确保在完成运行时检查后只对每个 "instantiate" NewType 进行检查,但它 是一种实际可用的方式,以类型检查器能够理解的形式对任意运行时检查的结果进行编码。
我最近一直在深入研究 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函数才能通过静态类型检查器。
但我不清楚这是如何工作的。例如,如果我可以指定一个类型必须支持的任何函数,或者该类型必须介于两者之间的范围,我可以看到这非常强大。
我的问题如下:有没有办法使用绑定来支持任何随机函数要求?例如,该类型必须支持 len
功能? (我怀疑这是可能的)
特定变量类型的范围如何(即小于 10 个字符的字符串,或小于 100 的整数)? (我怀疑这是不太可能的)
核心规则是:绑定需要是某种合法的 PEP-484 类型。
通常,所有这一切都是让您指定 T 最终必须 "filled" 由绑定或绑定的某个子类。例如:
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.
来做一些更有趣的事情基本上,假设您有这样一个程序:
class SupportsFoo:
def foo(self, x: int) -> str: ...
class Blah:
def foo(self, x: int) -> str: ...
# The two types are not related, so this fails with a
# 'Incompatible types in assignment' error -- the RHS needs
# to be a subtype of the declared type of the LHS.
x: SupportsFoo = Blah()
这两个 类 被 mypy 视为完全无关:它们可能碰巧共享一个具有相同签名的函数 foo
,但 Blah
不继承自SupportsFoo
反之亦然,因此它们的相似性被视为巧合,因此被丢弃。
我们可以通过将 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。 (Typeshed 的副本被烘焙到每个 mypy 版本中):
@runtime_checkable
class SupportsAbs(Protocol[_T_co]):
@abstractmethod
def __abs__(self) -> _T_co: ...
是的,正如您所要求的,您还可以创建一个协议来坚持输入类型使用 typing.Sized
实现 __len__
,其定义如下:
@runtime_checkable
class Sized(Protocol, metaclass=ABCMeta):
@abstractmethod
def __len__(self) -> int: ...
是的,您的直觉是没有一种干净的方法来创建断言 "this string must be 10 characters or less" 或 "this must be an int less then 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)
这不是一个完美的解决方案,因为它要求您在 runtime/requires 处进行检查,以确保在完成运行时检查后只对每个 "instantiate" NewType 进行检查,但它 是一种实际可用的方式,以类型检查器能够理解的形式对任意运行时检查的结果进行编码。