2

Consider the following code:

#!/usr/bin/env python3
import time
from typing import Dict, Union, Any, cast, Callable

some_variable = {'aaa': '123', 'zzz': 1}  # type: Dict[str, Union[str, int]]

# ensure that some_variable['zzz'] is an int
some_variable['zzz'] = int(some_variable['zzz'])

sleep_time = some_variable['zzz']  # type: int

time.sleep(sleep_time)

Clearly, by the time sleep_time is defined, the type of some_variable['zzz'] is an int. Yet, mypy gives an error: Incompatible types in assignment (expression has type "Union[str, int]", variable has type "int").

My questions is: how to tell mypy that the type is int at that point in the code? And better: how to tell mypy that the type is some complex data structure at that point in the code?

Notes:

  1. As far as I know, there is no way to tell mypy about the data structure for a specific key in dict during initialization of the dict, so I like to tell mypy about the structure when I process it. That's what I try to do with sleep_time = some_variable['zzz'] # type: int.

  2. assert isinstance(some_variable['zzz'], int) does suppress the error. Unfortunately, I can't use isinstance, since the actual data type in my code is not an int, but more complex data structure. Adding a line some_variable['zzz'] = cast(int, some_variable['zzz']) or some_variable['zzz'] = int(some_variable['zzz']) does not seem to make a difference to mypy. That's too bad, as cast() can take complex data structures, and I can define a complex data structure as the output of a type coercion function.

  3. To my surprise, some_variable = {'aaa': '123', 'zzz': 1} # type: Dict[str, Any] (thus with Any instead of Union[str,int]) silences any errors by mypy.

  4. Adding type: int to the line some_variable['zzz'] = int(some_variable['zzz']) gives an error: Unexpected type declaration.

  5. FYI, my actual codes is more akin to the following, where some_variable is read from file, but with more complex normalisation functions than str.lower() and int().

mappers = {
    'aaa': str.lower,
    'zzz': int
}  # type: Dict[str, Callable[[Any], Union[str, int]]]
defaults = {
    'aaa': '',
    'zzz': 2
}  # type: Dict[str, Union[str, int]]
for key, value in some_variable.items():
    try:
        some_variable[key] = mappers[key](value)
    except Exception:
        some_variable[key] = defaults[key]
Georgy
  • 12,464
  • 7
  • 65
  • 73
MacFreek
  • 3,207
  • 2
  • 31
  • 41
  • 1
    Can you use [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict)? – Georgy Jul 20 '20 at 09:52
  • @Georgy thanks, good suggestion. Feel free to turn this into an answer. Note that is only available starting with Python 3.8 (for other reasons I like to be backward compatible with 3.7, but perhaps I can make mypy work with 3.8, and the running code with 3.7) – MacFreek Jul 20 '20 at 11:03
  • Hmm... If the `TypedDict` is what you are looking for, then, I guess, the question should be closed as a duplicate of [Python 3 dictionary with known keys typing](https://stackoverflow.com/q/44225788/7851470). I'll leave it to subject matter experts, though. – Georgy Jul 20 '20 at 11:34
  • No, TypedDict is a workaround at best. What I'm looking for is https://mypy.readthedocs.io/en/latest/common_issues.html#redefinitions-with-incompatible-types ("How to redefine a variable with a more precise or a more concrete type") Given that it is the manual, it should work. I just failed to see why it throws an error in my code above. – MacFreek Jul 20 '20 at 13:00

0 Answers0