Search code examples
pythonmypypython-typing

mypy error: Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", target has type "List[str]")


I tried to instantiate an empty dictionary on the second level of an existing dict, then assign a key-value pair to it, but MyPy throws an error.

Here is a minimal example, which will reproduce it when MyPy checking is activated:

result = {"Test": "something"}
result['key'] = {}
result['key']['sub_key'] = ["some string", "another string"]

The error here will be something like:

mypy(error): Incompatible types in assignment (expression has type
"Dict[<nothing>, <nothing>]", target has type "List[str]")

How do I prevent this error? According to a similar problem, it was suggested to do

result['key'] = {}  # type: ignore

as a workaround, but this does not seem very elegant, which is why I'm wondering if there is more one can do.


Solution

  • The problem

    Right, so let's look at the first two lines here.

    First, you define your dictionary result. You define it like so:

    result = {"Test": "something"}
    

    You don't declare what types you expect the keys and values of result to have, so MyPy is left to work it out for itself. Alright, it says, I can do this — you've got only strings as dictionary keys, and only strings as dictionary values, therefore result must be of type dict[str, str].

    Then we get to line 2:

    result['key'] = {}
    

    And MyPy, quite reasonably, raises an error. "Hey, this looks like a mistake!" it says. You've only got strings as dictionary values in this so far, and you haven't explicitly told MyPy that it's okay to have non-str values in this dictionary, so MyPy reckons you've probably made an error here, and didn't mean to add that value to the dictionary.

    No real need to look at the third line, because it's basically the same thing going on

    How to fix this?

    There are a couple of ways of fixing this. In order of most-preferred (use if possible) to least-preferred (use only as a last resort):

    1. You can tell MyPy that this specific dictionary has certain types associated with certain string-keys by annotating it as a TypedDict. (You'll have to change your "sub-key" key to "sub_key", however, as "sub-key" isn't a valid variable name.)

      from typing import TypedDict
      
      class KeyDict(TypedDict, total=False):
          sub_key: list[str]
      
      
      class ResultDict(TypedDict, total=False):
          Test: str
          key: KeyDict
      
      
      result: ResultDict = {"Test": "something"}
      result['key'] = {}
      result['key']['sub_key'] = ["some string", "another string"]
      
    2. You can tell MyPy that any value in this dictionary could be of type str, or it could be of type dict[str, list[str]]:

      from typing import Union
      
      result: dict[str, Union[str, dict[str, list[str]]]] = {"Test": "something"}
      d: dict[str, list[str]] = {}
      d['sub_key'] = ["some string", "another string"]
      result['key'] = d
      
    3. You can tell MyPy that the value in this dictionary could be anything (not that different from switching off the type-checker):

      from typing import Any
      
      result: dict[str, Any] = {"Test": "something"}
      result['key'] = {}
      result['key']['sub_key'] = ["some string", "another string"]
      
    4. You can switch off the type-checker:

      result = {"Test": "something"}  
      result['key'] = {}  # type: ignore[assignment]
      result['key']['sub_key'] = ["some string", "another string"] # type: ignore[index]
      

    Anyway, it's a bit difficult to know what the "best" solution is here without knowing more about your use case.