我正在尝试定义一个接受所有实数的自定义类型(用于Python类型提示)。实际上,我想支持任何允许有意义比较的数字类型,但坚持只使用实数就足够了。然而,我这样做的方式似乎并不正确。例如,使用以下代码片段:
import numbers
import typing
MyDataType = typing.NewType('MyDataType', numbers.Real)
def f(a : MyDataType) -> None:
pass
f(5)
我的朋友抱怨说:
“f”的参数 1 具有不兼容的类型“int”;预期为“MyDataType”
我如何才能真正实现我想要做的事情?
numbers
包中的类是抽象基类(ABC)(即无法实例化),类型int
和float
分别注册为numbers.Integral
和numbers.Real
的虚拟子类。注册虚拟子类允许 isinstance
和 issubclass
读取它(在 docs 中了解有关 ABC 的更多信息)。
>>> issubclass(int, numbers.Real)
True
>>> issubclass(float, numbers.Real)
True
但是,由于
int
和 float
已注册为虚拟子类,因此 mypy
目前无法将 int
和 float
解析为 numbers.Real
的有效子类,如访问 cls.__mro__
所示:
>>> int.__mro__
(<class 'int'>, <class 'object'>)
>>> float.__mro__
(<class 'float'>, <class 'object'>)
>>> numbers.Integral.__mro__
(<class 'numbers.Integral'>, <class 'numbers.Rational'>, <class 'numbers.Real'>, <class 'numbers.Complex'>, <class 'numbers.Number'>, <class 'object'>)
>>> numbers.Real.__mro__
(<class 'numbers.Real'>, <class 'numbers.Complex'>, <class 'numbers.Number'>, <class 'object'>)
在阅读了一大堆
mypy
代码后,似乎可能存在一个错误,即未将内置类型检查为虚拟子类。理论上, mypy
应该将 int
和 float
识别为有效子类,并且可以使用 bound
中的 TypeVar
关键字来回答允许任何子类型的其他问题。
import numbers
import typing
MyDataType = typing.TypeVar('MyDataType', bound=numbers.Real)
def f(a: MyDataType) -> None:
pass
f(5) # in reality, mypy returns a type-var error since it cannot resolve "int" as a valid subclass of "numbers.Real"
要使用
int
和 float
类型(包括任何其他类型),请将每种类型添加为 TypeVar
的参数。
import typing
MyDataType = typing.TypeVar('MyDataType', int, float)
def f(a: MyDataType) -> None:
pass
f(5) # Success: no issues found in 1 source file
或者,如果您使用的是 Python 3.12,现在可以使用以下语法:
def f[T: int, float](a: T) -> None:
pass
f(5) # Success: no issues found in 1 source file
如果您想使用此语法,请确保在运行
--enable-incomplete-feature=NewGenericSyntax
时包含标志 mypy
。