mypy报错‘**后的参数必须是映射而非object’原因排查
问题描述
我编写的示例代码可正常输出预期结果,但在实际业务代码中,两个结构类似的Pydantic类里,执行dict(EmeterStat(**x).datekv() for x in emeter_stat_list)时,mypy抛出错误:error: Argument after ** must be a mapping, not "object",另一个类无此报错,且两类代码运行均符合预期,无法找到两类代码的差异,请求排查该报错原因。
示例代码
class MyClass: k: int v: int def __init__(self, *, k, v): self.k = k self.v = v def getkv(self): return self.k, self.v def test(): a = [{"k": 5, "v": 25}, {"k": 7, "v": 49}, {"k": 9, "v": 81}] mcl = dict(MyClass(**x).getkv() for x in a) print(a) print(mcl) if __name__ == "__main__": test()
实际报错精简代码
from typing import Any, Optional from pydantic import BaseModel, PositiveFloat, ValidationError, conint, root_validator class UsageStat(BaseModel): year: conint(ge=1970, le=2100) # type: ignore month: conint(ge=1, le=12) # type: ignore day: Optional[conint(ge=1, le=31)] # type: ignore time: conint(ge=0, le=44640) # type: ignore def datekv(self): return self.day if self.is_daily else self.month, self.time class EmeterStat(BaseModel): year: conint(ge=1970, le=3000) # type: ignore month: conint(ge=1, le=12) # type: ignore day: Optional[conint(ge=1, le=31)] # type: ignore energy: PositiveFloat # type: ignore # kW (kilo Watts) def datekv(self, *, kwh: bool = True): """Return key,value pair for period index, energy.""" k = self.day if self.is_daily else self.month v = self.energy if kwh else int(self.energy * 1000) return k, v # Removed some property getters class Config: validate_assignment = True @root_validator(pre=True) def _convert_energy(cls, values: dict[str, Any]) -> dict[str, Any]: # removed code to simplify def test(): usage_stat_list = [ {"year": 2022, "month": 9, "time": 3}, {"year": 2022, "month": 10, "time": 4}, {"year": 2022, "month": 11, "time": 5}, ] usl = dict(UsageStat(**x).datekv() for x in usage_stat_list) emeter_stat_list = [ {"year": 2022, "month": 9, "energy": 0.3}, {"year": 2022, "month": 10, "energy_wh": 400}, {"year": 2022, "month": 11, "energy": 0.5}, ] # This is the line it complains about usl = dict(EmeterStat(**x).datekv() for x in emeter_stat_list)
报错原因及解决方案
核心原因
mypy无法识别Pydantic根验证器的运行时逻辑,导致类型推断异常:
emeter_stat_list中的第二个元素包含energy_wh键,但EmeterStat类定义的字段是energy,虽然你通过_convert_energy根验证器可以把energy_wh转换为energy,但mypy无法理解这个运行时处理逻辑,因此认为该元素不符合EmeterStat的构造参数要求,进而将x的类型推断为object而非合法的mapping类型,最终抛出错误。- 对比
UsageStat,其列表中的所有元素键都严格匹配类定义的字段,mypy能正常推断x为合法的mapping,因此无报错。
解决方案
- 显式标注列表类型:给
emeter_stat_list加上list[dict[str, Any]]的类型注解,明确告知mypy该列表元素是字典类型:
emeter_stat_list: list[dict[str, Any]] = [ {"year": 2022, "month": 9, "energy": 0.3}, {"year": 2022, "month": 10, "energy_wh": 400}, {"year": 2022, "month": 11, "energy": 0.5}, ]
- 使用TypedDict定义输入结构:如果想更精确地约束输入类型,可以定义包含
energy和energy_wh的TypedDict:
from typing import TypedDict class EmeterInput(TypedDict, total=False): year: int month: int day: Optional[int] energy: float energy_wh: int emeter_stat_list: list[EmeterInput] = [ {"year": 2022, "month": 9, "energy": 0.3}, {"year": 2022, "month": 10, "energy_wh": 400}, {"year": 2022, "month": 11, "energy": 0.5}, ]
- 临时忽略mypy错误:如果不想修改类型注解,可在报错行添加
# type: ignore(不推荐长期使用,会掩盖其他潜在类型问题):
usl = dict(EmeterStat(**x).datekv() for x in emeter_stat_list) # type: ignore
内容的提问来源于stack exchange,提问作者Jules
相关产品推荐
相关产品推荐

