考虑以下两个注释:
def foo1(arg: tuple[datetime, int] | tuple[datetime, None]) -> datetime:
...
def foo2(arg: tuple[datetime, int | None]) -> datetime:
...
有理由使用其中一种而不是另一种吗?(除了偏好)
据我所知,它们在逻辑上是等价的;他们是吗?
两者并不完全等同。
将
tuple[datetime, int | None]
传递给第一个变体是无效的,但对第二个变体有效。
给定
def foo1(arg: tuple[datetime, int] | tuple[datetime, None]) -> datetime:
...
def foo2(arg: tuple[datetime, int | None]) -> datetime:
...
代码
x: tuple[datetime, int | None] = ...
foo1(x)
foo2(x)
使用 mypy 进行类型检查失败并显示消息
error: Argument 1 to "foo1" has incompatible type "tuple[datetime, int | None]"; expected "tuple[datetime, int] | tuple[datetime, None]" [arg-type]
这是否有用取决于您的用例。
正如 Brian 在 this answer 中指出的那样,对于某种类型中的某些
C
泛型,不能保证 C[T | S]
与任何 C[T] | C[S]
、T
的 S
兼容。
但是,这不仅会影响所有外部调用者,还会影响实现本身。考虑以下片段:
from datetime import datetime
def foo1(arg: tuple[datetime, int] | tuple[datetime, None]) -> None:
if arg[1] is None:
reveal_type(arg) # N: Revealed type is "tuple[datetime.datetime, None]"
else:
reveal_type(arg) # N: Revealed type is "tuple[datetime.datetime, builtins.int]"
def foo2(arg: tuple[datetime, int | None]) -> None:
if arg[1] is None:
reveal_type(arg) # N: Revealed type is "tuple[datetime.datetime, Union[builtins.int, None]]"
else:
reveal_type(arg) # N: Revealed type is "tuple[datetime.datetime, Union[builtins.int, None]]"
x: tuple[datetime, int | None]
foo1(x) # E: Argument 1 to "foo1" has incompatible type "tuple[datetime, int | None]"; expected "tuple[datetime, int] | tuple[datetime, None]" [arg-type]
foo2(x)
注意类型如何在两个函数中缩小。
在前一种情况下,您知道参数的类型为
tuple[datetime, int]
OR tuple[datetime, None]
。因此,如果最后一项是 None
,您实际上可以将类型缩小到仅 tuple[datetime, None]
,并且它会发生。
在后者中,类型保持不变,尽管形式上这种缩小是安全的(如果我没有错过任何东西)。相同的
is None
检查不会将 tuple[datetime, int | None]
缩小为 tuple[datetime, None]
。这个实现在mypy
和pyright
中是一致的,所以在union情况下类型没有缩小可能有更深层次的原因。