Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mypy error on dict of dict: Value of type "object" is not indexable

I have the following dictionary on python:

dictionary = {
    'key1': 1,
    'sub_dict': {'key2': 0},
}

When I run mypy on the following line:

print(dictionary['sub_dict']['key2'])

it raises the error Value of type "object" is not indexable

like image 658
Pierre S. Avatar asked Feb 20 '19 12:02

Pierre S.


1 Answers

Static typing is tricky. mypy can determine that the values of dictionary don't all have the same type, but that's as far as it goes. The static type of dictionary is Dict[str,object], based on the initial value. However, mypy doesn't try to simulate the code further, which means that it has no idea if d['sub_dict'] is still another dict at the point where you try to index it with key2, which leads to the type error.

One thing you can do is help mypy by telling it that a particular value can be treated as having a specific type, using typing.cast.

print(typing.cast(typing.Dict[str,dict], d['sub_dict'])['key2'])

At runtime, typing.cast is effectively an identity function; it just returns its second argument. mypy treats it like a stronger type hint, saying that regardless of any previous hints or annotations, d['sub_dict'] should be treated as a Dict[str,dict].

Note, though, that by using cast, you are telling mypy that you are assuming responsibility for assuring that dictionary['sub_dict'] is, in fact, a dict at runtime, since this isn't something that you can convey with a static type. You might think that something like

dictionary : Dict[str,Union[int,dict]] = ...

would work, but that's just telling mypy that it would be a type error to write dictionary['foo'] = 'bar', since 'bar' is neither an int or a dict. Even with the more accurate type hint, there's still no way for mypy to know what type of value dictionary maps any specific key to.

You could use Any as well:

dictionary: Dict[str,Any] = ...

because now you are saying that any type can be used as a value, and that any type can be assumed for the result of indexing, and the two types don't have to line up. That is, dictionary['key1'] = 3 is fine because int is compatible with Any, but dictionary['sub_dict']['key2'] is also fine because whatever dictionary['sub_dict'] produces is also compatible with Any, and you can assume that that type is itself indexible. In effect, it covers any use of dictionary anywhere in your code, instead of a specific location where you used cast to make assertions about what should be allowed.


Major digression: there is a notion of dependent types, the simplest example of which is a type like PositiveInt which would be identical to int except it doesn't permit negative values. dictionary would seem to have a similar dependent type, where the type of the values is really a function of the actual data stored in a value. For example, imagine if you could use an instance of dict with Dict to specify the type of its values.

dictionary: Dict[str, {"key1": int, "sub_dict": dict}] = {'key1': 1,
          'sub_dict': {'key2': 0}
         }

Now, not only could mypy tell that dictionary['key1'] is supposed to be an int, but that dictionary itself can never have any keys other than key1 and sub_dict. (And in this hypothetical world, a defaultdict could map an arbitrary unspecified key to a default type.)

like image 184
chepner Avatar answered Nov 02 '22 20:11

chepner