You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,因此无报错。

解决方案

  1. 显式标注列表类型:给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},
]
  1. 使用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},
]
  1. 临时忽略mypy错误:如果不想修改类型注解,可在报错行添加# type: ignore(不推荐长期使用,会掩盖其他潜在类型问题):
usl = dict(EmeterStat(**x).datekv() for x in emeter_stat_list)  # type: ignore

内容的提问来源于stack exchange,提问作者Jules

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 22:15:57