37

My function looks like this simplified code sample:

def my_func() -> dict:
    result = {"success": False}

    if condition:
        result["success"] = True
        return result
    else:
        result["message"] = "error message"
    return result

When I run Mypy (version 0.52) I get this error:

error: Incompatible types in assignment (expression has type "str", target has type "bool")

and the error is pointing to the second last line in my code sample. Why mypy is returning this error? is my code invalid (in any way) or is this some mypy bug?

Jan Rozycki
  • 1,603
  • 2
  • 15
  • 19

1 Answers1

79

The problem is that mypy inferred that the type of your result variable is Dict[str, bool] due to how you first initialized it on line 2.

Consequently, when you try and insert a str later, mypy (rightfully) complains. You have several options for fixing your code, which I'll list in order of least to most type-safe.

Option 1 is to declare your dictionary such that its values are of type Any -- that is, your values will not be type-checked at all:

from typing import Any, Dict

def my_func(condition: bool) -> Dict[str, Any]:
    result = {"success": False}  # type: Dict[str, Any]

    if condition:
        result["success"] = True
    else:
        result["message"] = "error message"
    return result

Note that we needed to annotate your second line to give mypy a hint about what the type of result should be, to help its inference process.

If you're using Python 3.6+, you can annotate that line using the following alternate syntax, which uses variable annotations (which are new as of Python 3.6):

result: Dict[str, Any] = {"success": False}

Option 2 is slightly more type-safe -- declare your values to be either strs or bools, but nothing else, using Union. This isn't fully typesafe, but at least you can still have some checks on your dict.

from typing import Any, Dict

def my_func(condition: bool) -> Dict[str, Union[str, bool]]:
    result = {"success": False}  # type: Dict[str, Union[str, bool]]

    if condition:
        result["success"] = True
    else:
        result["message"] = "error message"
    return result

You may perhaps find that type annotation to be a little long/annoying to type, so you could use type aliases for readability (and optionally use the variable annotation syntax), like so:

ResultJson = Dict[str, Union[str, bool]]

def my_func(condition: bool) -> ResultJson
    result: ResultJson = {"success": False}
    # ...snip...

If you are ok with dropping compatibility with older versions of Python, you can shrink your alias slightly more by using some new syntax introduced in Python 3.10:

# Python 3.10 lets you use 'dict' instead of Dict
ResultJson = dict[str, str | bool]

def my_func(condition: bool) -> ResultJson
    result: ResultJson = {"success": False}
    # ...snip...

Option 3 is the most type-safe: you can assign specific types to different fields in your dict using a TypedDict:

from typing import Optional, TypedDict

# You can also use the older:
# 
#     ResultJson = TypedDict("ResultJson", {...})
#
# ...syntax if you need to support versions of Python
# older then 3.6 or need to support keys that are not
# valid Python identifiers.
class ResultJson(TypedDict):
    success: bool
    message: Optional[str]

def my_func(condition: bool) -> ResultJson:
    result: ResultJson = {"success": False, "message": None}

    if condition:
        result["success"] = True
    else:
        result["message"] = "error message"
    return result

The main caveat is that typed_dict was added in Python 3.8. If you need to support older versions of Python, pip-install the typing_extensions package and use from typing_extensions import TypedDict instead.

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
  • Thanks for this comprehensive answer Michael! I think I struggled with certain concepts of mypy logic, but you really cleared this up for me. Sidenote: do you know how to deal with inline type hinting comments when the line gets longer than 79 chars allowed by PEP8? (I can't use Python3.6 for this project, so the new variable annotations syntax is not available for me) – Jan Rozycki May 15 '17 at 07:47
  • 1
    @JanRozycki Careful use of [type aliases](http://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases) can usually help alleviate that issue. When typing functions with many parameters, typing your function [like this](http://mypy.readthedocs.io/en/latest/python2.html#multi-line-python-2-function-annotations) can also be helpful. – Michael0x2a May 15 '17 at 18:43
  • 3
    **[`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict)** was added to the standard library in python 3.8 – Lord Elrond Nov 07 '20 at 15:17