Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mypy set dictionary keys / interface

Suppose I have a function which takes a dictionary as a parameter:

def f(d: dict) -> None:
    x = d["x"]
    print(x)

Can I specify that this dictionary must have the key "x" to mypy? I'm looking for something similar to interface from typescript, without changing d to a class.

The reason I don't want to change d to a class, is because I am modifying a large existing codebase to add mypy type checking and this dictionary is used in many places. I would have to modify a lot of code if I had to change all instances of d["x"] to d.x.

like image 208
Daniel Kats Avatar asked Jan 28 '23 00:01

Daniel Kats


1 Answers

As of Python 3.8 you can use typing.TypedDict, added as per PEP 589. For older Python versions you can use the typing-extensions package

Note that the PEP does acknowledge that the better option is that you use dataclasses for this use-case, however:

Dataclasses are a more recent alternative to solve this use case, but there is still a lot of existing code that was written before dataclasses became available, especially in large existing codebases where type hinting and checking has proven to be helpful.

So the better answer is to consider a different data structure, such as a named tuple or a dataclass, where you can specify the attribute a type has. This is what the typescript declaration does, really:

The printLabel function has a single parameter that requires that the object passed in has a property called label of type string.

Python attributes are the moral equivalent of Typescript object properties. That Typescript object notation and Python dictionaries have a lot in common perhaps confuses matters, but you should not look upon Typescript object declarations as anything but classes, when trying to map concepts to Python.

That could look like this:

from dataclasses import dataclass

@dataclass
class SomeClass:
    x: str

def f(sc: SomeClass) -> None:
    x = sc.x
    print(x)

That said, you can use typing.TypedDict here:

from typing import TypedDict

class SomeDict(TypedDict):
    x: str

def f(d: SomeDict) -> None:
    x = d['x']
    print(x)

Keys in a TypeDict declaration are either all required, or all optional (when you set total=False on the declaration); you'd have to use inheritance to produce a type with some keys optional, see the documentation linked. Note that TypedDict currently has issues with a mix of optional and required keys; you may want to use the typing-extensions package to get the Python 3.9 version (which fixes this) as a backport even when using Python 3.8. Just use from typing_extensions import TypedDict instead of the above from typing ... import, the typing-extensions package falls back to the standard library version when appropriate.

like image 68
Martijn Pieters Avatar answered Jan 30 '23 15:01

Martijn Pieters