Search code examples
pythonmypy

Why does mypy report error: Argument after ** must be a mapping, not "object"


I have code that looks something like this:

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()

The output is:

[{'k': 5, 'v': 25}, {'k': 7, 'v': 49}, {'k': 9, 'v': 81}]
{5: 25, 7: 49, 9: 81}

In my actual code I have two classes of this form, and for one of them mypy gives the error:

error: Argument after ** must be a mapping, not "object"

for this line:

mcl = dict(MyClass(**x).getkv() for x in a)

I have spent some time editing the classes to try to make them identical and testing for differences but I can't find anything and mypy does not complain about the code in this sample.

Can anyone suggest a reason why mypy might be objecting? In both cases my test code produces the same expected output.

Thanks

For completeness here is the code that actually fails stripped to shorten it:

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)

Solution

  • For what mypy knows, a might be a list of anything. Help it by typing it as a list of dicts:

    from typing import List
    
    ...
    
    a: List[dict] = [{"k": 5, "v": 25}, {"k": 7, "v": 49}, {"k": 9, "v": 81}]