Search code examples
pythondictionarymypy

How to get mypy to accept an unpacked dict?


I am having problems with mypy.

I have this code:

func(arg1, arg2, arg3=0.0, arg4=0.0)
# type: (float, float, float, float) -> float
# do something and return float.

dict_with_other_arguments = {arg3: 0.5, arg4: 1.4}
a = func(arg1, arg2, **dict_with_other_arguments)

The problem is that mypy does not check what's in the dictionary for types, instead, I get an error like this:

error: Argument 3 to "func" has incompatible type "**Dict[str, float]"; expected "float"

Any ideas how to fix this without changing code?


Solution

  • Mypy is correct in flagging your function calls. The following code illustrates why:

    def func(str_arg='x', float_arg=3.0):
      # type: (str, float) -> None
      print(str_arg, float_arg)
    
    kwargs1 = {'float_arg': 8.0}
    kwargs2 = {'str_arg': 13.0}  # whoops
    
    func(float_arg=5.0)  # prints "x 5.0" -- good
    func(**kwargs1)      # prints "x 13.0" -- good but flagged by Mypy
    func(**kwargs2)      # prints "13.0 3.0" -- bad
    

    In this example, kwargs1 and kwargs2 are both of type Dict[str, float]. The type checker does not consider the content of the keys, only their types, so the second and third calls to func look identical to Mypy. They must either both be errors or both be acceptable, and they can't both be acceptable since the third call violates the type system.

    The only way that the type checker can be sure that you're not passing incorrect types in the dict is if all of the arguments that haven't been explicitly passed share the type of the dict's values. Note, however, that mypy will not protect you from errors caused by respecifying a keyword argument in a dict:

    # This works fine:
    func('x', **kwargs1)
    # This is technically type safe and accepted by mypy, but at runtime raises
    # `TypeError: func() got multiple values for argument 'str_arg'`:
    func('x', **kwargs2)
    

    There is some further discussion of this issue here.