mypy 允许在特定行重新定义
mypy allow redefinition at a specific line
我正在尝试使用 mypy 整理我的 python 代码。我依赖的一个实用程序使用一个参数可以采用多种类型的回调。类型通常由附带的主题字符串定义。
我遇到了一个问题,回调需要多个主题,每个主题都有唯一的类型。在这种情况下,我不确定如何与 mypy
通信,该类型比两者的联合更受限制。
def callback(self, topic: str, msg: Union[A, B]):
if topic == "a":
msg: A
# Do something specific to topic "a"
if topic == "b":
msg: B
# Do something specific to topic "b"
我试过写成 msg: A # type: ignore
,但是 mypy 忽略了这一行。由于该实用程序的结构,我也无法使用 isinstance
。
可能的解决方案
我认为有两件事应该可行,但我不确定 mypy
是否具有这样的功能:
- 允许在特定行上重新定义。当 运行
--allow-redefinition
错误被抑制。不幸的是,运行 在我看来,整个包裹太松了,所以我宁愿避免它。
- 将变量的类型绑定到另一个变量的值。我不确定它是否存在,但它可能类似于
TypedDict
,其中键可以绑定到不同类型的值,而不是两个单独的变量。
这需要一些恼人的样板代码和一些技巧,但您可以使用 PEP 647 TypeGuard 来完成。
TypeGuard 的存在是为了告诉 MyPy 某些自定义函数能够以类似于 isinstance 的方式进行类型缩小。例如,您可能有一个接受列表的函数,如果该列表中的所有内容都是整数,则 returns 为真:
def are_all_ints(probe: List[Any]) -> TypeGuard[List[int]]:
return all(isinstance(int, p) for p in probe)
xs: List[Any] = ...
if are_all_ints(xs):
# MyPy trusts the check was done correctly and therefore
reveal_type(xs) # this gives List[int] within this section
如您所见,TypeGuard 对传递给它们的变量进行操作。如果将 topic
字符串传递给合适的类型保护函数,则可能会变窄,但不会变窄 msg
。解决方法是将它们打包到一个临时变量中。
def callback(self, topic: str, msg: Union[A, B]):
def is_callback_from_a(p: Tuple[str, Union[A, B]]) -> TypeGuard[Tuple[Literal["a"], A]]:
return p[0] == "a"
def is_callback_from_b(p: Tuple[str, Union[A, B]]) -> TypeGuard[Tuple[Literal["b"], B]]:
return p[0] == "b"
# Package the two parameters into one variable so that
# the guard clears them simulataneously
topic_msg_pair = (topic, msg)
if is_callback_from_a(topic_msg_pair):
...
# topic_msg_pair[1] is known to be an A here
elif is_callback_from_b(topic_msg_pair):
...
# topic_msg_pair[1] is known to be a B here
Overload signatures 将使您的函数在外部正确,即使您必须在内部进行转换。
class Example:
@overload
def callback(self, topic: Literal["a"], msg: A) -> None: ...
@overload
def callback(self, topic: Literal["b"], msg: B) -> None: ...
def callback(self, topic: str, msg: Union[A, B]):
if topic == "a":
a_msg = cast(A, msg)
reveal_locals()
if topic == "b":
b_msg = cast(B, msg)
reveal_locals()
(注意:Literal
已添加到 Python 3.8 中的 typing
。如果您使用的是早期版本,可以从 typing_extensions
获取)
在函数内部,我们仍然必须显式转换,但在外部,调用者将始终需要使用 (1) 文字“a”和 A
或 (2 ) 文字 "b" 和 a B
.
我正在尝试使用 mypy 整理我的 python 代码。我依赖的一个实用程序使用一个参数可以采用多种类型的回调。类型通常由附带的主题字符串定义。
我遇到了一个问题,回调需要多个主题,每个主题都有唯一的类型。在这种情况下,我不确定如何与 mypy
通信,该类型比两者的联合更受限制。
def callback(self, topic: str, msg: Union[A, B]):
if topic == "a":
msg: A
# Do something specific to topic "a"
if topic == "b":
msg: B
# Do something specific to topic "b"
我试过写成 msg: A # type: ignore
,但是 mypy 忽略了这一行。由于该实用程序的结构,我也无法使用 isinstance
。
可能的解决方案
我认为有两件事应该可行,但我不确定 mypy
是否具有这样的功能:
- 允许在特定行上重新定义。当 运行
--allow-redefinition
错误被抑制。不幸的是,运行 在我看来,整个包裹太松了,所以我宁愿避免它。 - 将变量的类型绑定到另一个变量的值。我不确定它是否存在,但它可能类似于
TypedDict
,其中键可以绑定到不同类型的值,而不是两个单独的变量。
这需要一些恼人的样板代码和一些技巧,但您可以使用 PEP 647 TypeGuard 来完成。
TypeGuard 的存在是为了告诉 MyPy 某些自定义函数能够以类似于 isinstance 的方式进行类型缩小。例如,您可能有一个接受列表的函数,如果该列表中的所有内容都是整数,则 returns 为真:
def are_all_ints(probe: List[Any]) -> TypeGuard[List[int]]:
return all(isinstance(int, p) for p in probe)
xs: List[Any] = ...
if are_all_ints(xs):
# MyPy trusts the check was done correctly and therefore
reveal_type(xs) # this gives List[int] within this section
如您所见,TypeGuard 对传递给它们的变量进行操作。如果将 topic
字符串传递给合适的类型保护函数,则可能会变窄,但不会变窄 msg
。解决方法是将它们打包到一个临时变量中。
def callback(self, topic: str, msg: Union[A, B]):
def is_callback_from_a(p: Tuple[str, Union[A, B]]) -> TypeGuard[Tuple[Literal["a"], A]]:
return p[0] == "a"
def is_callback_from_b(p: Tuple[str, Union[A, B]]) -> TypeGuard[Tuple[Literal["b"], B]]:
return p[0] == "b"
# Package the two parameters into one variable so that
# the guard clears them simulataneously
topic_msg_pair = (topic, msg)
if is_callback_from_a(topic_msg_pair):
...
# topic_msg_pair[1] is known to be an A here
elif is_callback_from_b(topic_msg_pair):
...
# topic_msg_pair[1] is known to be a B here
Overload signatures 将使您的函数在外部正确,即使您必须在内部进行转换。
class Example:
@overload
def callback(self, topic: Literal["a"], msg: A) -> None: ...
@overload
def callback(self, topic: Literal["b"], msg: B) -> None: ...
def callback(self, topic: str, msg: Union[A, B]):
if topic == "a":
a_msg = cast(A, msg)
reveal_locals()
if topic == "b":
b_msg = cast(B, msg)
reveal_locals()
(注意:Literal
已添加到 Python 3.8 中的 typing
。如果您使用的是早期版本,可以从 typing_extensions
获取)
在函数内部,我们仍然必须显式转换,但在外部,调用者将始终需要使用 (1) 文字“a”和 A
或 (2 ) 文字 "b" 和 a B
.