Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I specify OrderedDict K,V types for Mypy type annotation?

I am using Python 3.5 together with Mypy to have some basic static checking for my script. Recently I refactored some methods to return OrderedDict, but ran into "'type' object is not subscriptable" error, when I tried to use return annotation with Key and Value types specified.

Reduced example:

#!/usr/bin/env python3.5

from collections import OrderedDict

# this works
def foo() -> OrderedDict:
    result = OrderedDict() # type: OrderedDict[str, int]
    result['foo'] = 123
    return result

# this doesn't
def foo2() -> OrderedDict[str, int]:
    result = OrderedDict() # type: OrderedDict[str, int]
    result['foo'] = 123
    return result

print(foo())

And this is python output when it is run:

Traceback (most recent call last):
  File "./foo.py", line 12, in <module>
    def foo2() -> OrderedDict[str, int]:
TypeError: 'type' object is not subscriptable

Mypy however has no problem with the type annotation in comment and will in fact warn if I try to do result[123] = 123.

What is causing this?

like image 972
Xarn Avatar asked Dec 18 '16 09:12

Xarn


4 Answers

There is no problem in mypy (at least, not in 0.501). But there is a problem with Python 3.6.0. Consider the following:

from collections import OrderedDict
from typing import Dict

def foo() -> Dict[str, int]:
    result: OrderedDict[str, int] = OrderedDict()
    result['two'] = 2
    return result

This code will both satisfy mypy (0.501) and Python (3.6.0). However, if you replace Dict with OrderedDict, then mypy will still be happy, but executing it will die with TypeError: 'type' object is not subscriptable.

It is interesting that the Python interpreter dies on seeing a subscripted OrderedDict in the function signature, but is happy to accept it in a variable type annotation.

At any rate, my workaround for this is to use Dict instead of OrderedDict in the function signature (and add a comment that this should be fixed if/when the Python interpreter will learn to accept the correct signature).

like image 199
Oren Ben-Kiki Avatar answered Nov 08 '22 21:11

Oren Ben-Kiki


As a workaround, you can also put the return type into a string to satisfy both Mypy and Python 3.6:

from collections import OrderedDict

def foo() -> 'OrderedDict[str, int]':
    result = OrderedDict()
    result['foo'] = 123
    return result
like image 32
Dion Avatar answered Nov 08 '22 21:11

Dion


What you can also try is using MutableMapping (like in this Answer: https://stackoverflow.com/a/44167921/1386610)

from collections import OrderedDict
from typing import Dict

def foo() -> MutableMapping[str, int]:
    result = OrderedDict() # type: MutableMapping[str, int]
    result['foo'] = 123
    return result
like image 2
Arany Avatar answered Nov 08 '22 21:11

Arany


I don't know which version allowed this, but a better solution as for March/24/2021, tested for Python 3.7.5:

from collections import OrderedDict
import typing

def foo() -> typing.OrderedDict[str, int]:
    result: typing.OrderedDict[str, int] = OrderedDict()
    result['two'] = 2
    return result

Enjoy all worlds!

like image 2
Gulzar Avatar answered Nov 08 '22 22:11

Gulzar