短路`or`场景下的类型收窄问题:两种参数组合的类型标注
Python类型标注优化:解决二选一参数的类型收窄问题
问题背景
我正在为以下Python代码添加类型标注。函数connect的设计逻辑为:要么同时传入zone和zones参数,要么传入file参数(两种情况二选一,不可同时传入):
import pathlib def get_file(zone: str, zones: dict[str, str]) -> pathlib.Path: pass def connect( zone: str | None = None, zones: dict[str, str] | None = None, file: pathlib.Path | None = None, ) -> bool: file = file or get_file(zone, zones)
但这段代码触发了mypy报错:
1. Argument of type "str | None" cannot be assigned to parameter "zone" of type "str" in function "_get_vpn_file" Type "str | None" cannot be assigned to type "str" Type "None" cannot be assigned to type "str" 2. Argument of type "dict[str, str] | None" cannot be assigned to parameter "zones" of type "dict[str, str]" in function "_get_vpn_file" Type "dict[str, str] | None" cannot be assigned to type "dict[str, str]" Type "None" cannot be assigned to type "dict[str, str]"
尝试编写参数检查函数_check_params_are_ok进行主动类型收窄,mypy仍报相同错误;添加明确断言后问题依旧。目前仅能通过cast强制类型转换解决,但这会降低代码可读性,希望找到更优的类型收窄方法。
解决方案
方法1:显式条件分支触发类型收窄
直接在函数内编写清晰的参数合法性检查,通过is None判断触发mypy的类型收窄:
import pathlib def get_file(zone: str, zones: dict[str, str]) -> pathlib.Path: pass def connect( zone: str | None = None, zones: dict[str, str] | None = None, file: pathlib.Path | None = None, ) -> bool: if file is None: # 明确校验参数组合的合法性 if zone is None or zones is None: raise ValueError("必须同时传入zone和zones,或者传入file参数") # 此时mypy可推断zone和zones为非None类型 file = get_file(zone, zones) # 后续业务逻辑 return True
这种方式无需额外工具,通过直观的条件判断让类型推断逻辑清晰,同时兼顾运行时的参数合法性校验。
方法2:使用函数重载约束参数组合
利用@overload装饰器定义两种合法的参数签名,从函数声明层面约束调用方式,帮助mypy准确推断类型:
import pathlib from typing import overload @overload def connect(zone: str, zones: dict[str, str], file: None = None) -> bool: ... @overload def connect(zone: None = None, zones: None = None, file: pathlib.Path) -> bool: ... def connect( zone: str | None = None, zones: dict[str, str] | None = None, file: pathlib.Path | None = None, ) -> bool: if file is None: # 重载约束下,此时zone和zones必然非None file = get_file(zone, zones) return True
重载的方式不仅解决类型推断问题,还能给函数调用者提供更明确的参数提示,避免非法调用。
方法3:用TypedDict封装参数组合
如果参数逻辑复杂,可通过TypedDict定义两种合法的参数结构,再结合Union统一函数参数类型:
import pathlib from typing import TypedDict, Union class ZoneParams(TypedDict): zone: str zones: dict[str, str] class FileParams(TypedDict): file: pathlib.Path def get_file(zone: str, zones: dict[str, str]) -> pathlib.Path: pass def connect(**kwargs: Union[ZoneParams, FileParams]) -> bool: if "file" in kwargs: file = kwargs["file"] else: # 可添加运行时校验确保参数完整性 zone = kwargs["zone"] zones = kwargs["zones"] file = get_file(zone, zones) return True
这种方式适合参数较多的场景,把零散的可选参数整合为结构化的类型,让参数组合逻辑更直观。
内容的提问来源于stack exchange,提问作者Yam Mesicka
相关产品推荐
相关产品推荐

