使用 Python 的输入模块为复合类型创建类型别名有更好的方法吗?
Is there a preferable way to create type aliases for compound types with Python's typing module?
我有一个只有一个参数的函数,它应该接受一个 int
或一个 None
作为参数。有几种方法可以为这种复合类型创建类型别名:
# test.py
import typing
IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
IntOrNone_2 = typing.Union[int, None]
def my_func1(xyz: IntOrNone_1):
return xyz
def my_func2(xyz: IntOrNone_2):
return xyz
my_func1(12)
my_func1(None)
my_func1(13.7)
my_func1('str')
my_func2(12)
my_func2(None)
my_func2(13.7)
my_func2('str')
两种方法都达到了我期望的效果,但是,相应的mypy
错误略有不同,但基本上具有相同的含义。
test.py:14: error: Value of type variable "IntOrNone_1" of "my_func1"
cannot be "float"
test.py:15: error: Value of type variable "IntOrNone_1" of "my_func1"
cannot be "str"
test.py:19: error: Argument 1 to "my_func2" has incompatible type
"float"; expected "Optional[int]"
test.py:20: error: Argument 1 to "my_func2" has incompatible type
"str"; expected "Optional[int]"
我倾向于使用第二种方法,因为它还会报告导致错误的参数。
这两种方法是否确实如我所想的那样等效,还是其中一种更受青睐?
这两种方法远非等效。您应该避免将 TypeVars 视为仅仅是别名——相反,它们是您在想要创建函数时使用的更特殊的形式 generic.
用一个例子来解释什么是 "generic function" 是最简单的。假设您想编写一个函数来接受某个对象(任何对象!)和 return 另一个 完全相同类型 的对象。你会怎么做?
我们可以做的一种方法是尝试使用 object
:
def identity(x: object) -> object:
return x
这让我们接近了,因为我们的 identity
函数至少可以接受任何字面意思(因为所有类型都继承自 Python 中的 object
)。然而,这个解决方案是有缺陷的:如果我们传入一个 int,我们会返回 object
,这不是我们想要的。
相反,我们需要的是一种让类型检查器了解这两种类型之间存在 "relationship" 的方法。这正是 TypeVar
派上用场的地方:
T = TypeVar('T')
def identity(x: T) -> T:
return x
我们的 TypeVar 'T' 在这种情况下充当 "placeholder" 可以绑定到我们想要的任何类型。因此,如果我们执行 identity(3)
,T
将 绑定 到 int
—— 因此类型检查器将理解 return类型也必须是 int
!
如果我们在参数类型提示中多次使用 T
,类型检查器将确保每次的类型都相同。
那么,下面的表达式是做什么的?
IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
好吧,事实证明有时向我们的特殊占位符类型添加 约束 会很有用。例如,您已约束 IntOrNone_1
,以便它只能绑定到 int
或 None
,而不能绑定到其他类型。
最后,回答你的最后一个问题:在你给出的示例中,你绝对应该使用 Union,而不是 TypeVars。
是否使用 Union 或 Union 的类型别名确实是个人喜好问题,但如果您不需要这种 "placeholder" 或 "generic" 行为,则不应该正在使用 TypeVars。
如果您想了解有关如何使用 TypeVars 的更多信息,mypy 文档有一个 section on generics。它涵盖了我跳过的几件事,包括如何制作泛型 类(不仅仅是泛型函数)。
我有一个只有一个参数的函数,它应该接受一个 int
或一个 None
作为参数。有几种方法可以为这种复合类型创建类型别名:
# test.py
import typing
IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
IntOrNone_2 = typing.Union[int, None]
def my_func1(xyz: IntOrNone_1):
return xyz
def my_func2(xyz: IntOrNone_2):
return xyz
my_func1(12)
my_func1(None)
my_func1(13.7)
my_func1('str')
my_func2(12)
my_func2(None)
my_func2(13.7)
my_func2('str')
两种方法都达到了我期望的效果,但是,相应的mypy
错误略有不同,但基本上具有相同的含义。
test.py:14: error: Value of type variable "IntOrNone_1" of "my_func1" cannot be "float"
test.py:15: error: Value of type variable "IntOrNone_1" of "my_func1" cannot be "str"
test.py:19: error: Argument 1 to "my_func2" has incompatible type "float"; expected "Optional[int]"
test.py:20: error: Argument 1 to "my_func2" has incompatible type "str"; expected "Optional[int]"
我倾向于使用第二种方法,因为它还会报告导致错误的参数。
这两种方法是否确实如我所想的那样等效,还是其中一种更受青睐?
这两种方法远非等效。您应该避免将 TypeVars 视为仅仅是别名——相反,它们是您在想要创建函数时使用的更特殊的形式 generic.
用一个例子来解释什么是 "generic function" 是最简单的。假设您想编写一个函数来接受某个对象(任何对象!)和 return 另一个 完全相同类型 的对象。你会怎么做?
我们可以做的一种方法是尝试使用 object
:
def identity(x: object) -> object:
return x
这让我们接近了,因为我们的 identity
函数至少可以接受任何字面意思(因为所有类型都继承自 Python 中的 object
)。然而,这个解决方案是有缺陷的:如果我们传入一个 int,我们会返回 object
,这不是我们想要的。
相反,我们需要的是一种让类型检查器了解这两种类型之间存在 "relationship" 的方法。这正是 TypeVar
派上用场的地方:
T = TypeVar('T')
def identity(x: T) -> T:
return x
我们的 TypeVar 'T' 在这种情况下充当 "placeholder" 可以绑定到我们想要的任何类型。因此,如果我们执行 identity(3)
,T
将 绑定 到 int
—— 因此类型检查器将理解 return类型也必须是 int
!
如果我们在参数类型提示中多次使用 T
,类型检查器将确保每次的类型都相同。
那么,下面的表达式是做什么的?
IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
好吧,事实证明有时向我们的特殊占位符类型添加 约束 会很有用。例如,您已约束 IntOrNone_1
,以便它只能绑定到 int
或 None
,而不能绑定到其他类型。
最后,回答你的最后一个问题:在你给出的示例中,你绝对应该使用 Union,而不是 TypeVars。
是否使用 Union 或 Union 的类型别名确实是个人喜好问题,但如果您不需要这种 "placeholder" 或 "generic" 行为,则不应该正在使用 TypeVars。
如果您想了解有关如何使用 TypeVars 的更多信息,mypy 文档有一个 section on generics。它涵盖了我跳过的几件事,包括如何制作泛型 类(不仅仅是泛型函数)。