能否使用Structural Pattern Matching实现类型收窄?
问题描述
借助PEP 622的结构化模式匹配(SPM)功能,我希望避免将egg以Mapping类型传入multiple函数,即通过SPM实现类型收窄。
根据PEP文档说明:
通配符
_可添加星号以匹配任意长度的序列。例如:
[*_]匹配任意长度的序列。
我编写了如下代码:
# egg 的类型为 typing.Sequence[typing.Mapping[str, typing.Any]] | typing.Mapping[str, typing.Any] match egg: case [*_]: print(multiple(egg)) case _: print(single(egg))
我原本认为,case [*_]分支中egg会被视为Sequence类型,case _分支中则为非Sequence类型。但运行mypy检查项目时出现如下错误:
file.py:19: error: Argument 1 to "multiple" has incompatible type "Union[Sequence[Mapping[str, Any]], Mapping[str, Any]]"; expected "Sequence[Mapping[str, Any]]" [arg-type] file.py:21: error: Argument 1 to "single" has incompatible type "Union[Sequence[Mapping[str, Any]], Mapping[str, Any]]"; expected "Mapping[str, Any]" [arg-type] Found 2 errors in 1 file (checked 1 source file)
以下是触发该mypy错误的最小可复现示例:
import typing SingleExecuteParams = typing.Mapping[str, typing.Any] MultiExecuteParams = typing.Sequence[SingleExecuteParams] AnyExecuteParams = MultiExecuteParams | SingleExecuteParams def multiple(spam: MultiExecuteParams) -> int: return 1 def single(spam: SingleExecuteParams) -> int: return 1 def main(egg: AnyExecuteParams) -> None: match egg: case [*_]: print(multiple(egg)) case _: print(single(egg))
已尝试的方案
使用case {}:
根据文档说明,映射模式中case {}:会匹配collections.abc.Mapping的实例,额外键会被忽略(且{**_}是无效写法),但替换后问题依旧。
匹配对象必须是collections.abc.Mapping的实例。即使未指定rest,对象中的额外键也会被忽略。这与序列模式不同,序列模式中额外元素会导致匹配失败。但映射与序列存在本质差异:它们具有自然的结构子类型行为,即传递带有额外键的字典通常可以正常工作。因此,映射模式中_是无效的;它始终是无操作的,移除后不会产生任何影响。
使用case ... if ...语法
用case _ if isinstance(egg, typing.Sequence):可以实现类型收窄,但这样就失去了使用SPM原生模式匹配的意义。
对比:if语句实现类型收窄
使用普通if语句时,mypy不会触发错误:
def main(egg: AnyExecuteParams) -> None: if isinstance(egg, collections.abc.Sequence): print(multiple(egg)) else: print(single(egg))
根据PEP文档中的匹配语义说明:
本质上,这等效于一串if ... elif ... else语句。
我认为两种写法应该是等价的,但实际mypy对SPM的处理却不一致。
内容的提问来源于stack exchange,提问作者Dorian Turba
相关产品推荐
相关产品推荐

