Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to type hint a dictionary with values of different types

When declaring a dictionary as a literal, is there a way to type-hint what value I am expecting for a specific key?

And then, for discussion: are there guiding principles around dictionary typing in Python? I'm wondering whether it is considered bad practice to mix types in dictionaries.


Here's an example:

Consider the declaration of a dictionary in a class's __init__ :

(Disclaimer: I realize in the example, some of the .elements entries would probably be more appropriate as class attributes, but it's for the sake of the example.)

class Rectangle:
    def __init__(self, corners: Tuple[Tuple[float, float]], **kwargs):
        self.x, self.z = corners[0][0], corners[0][1]
        self.elements = {
            'front': Line(corners[0], corners[1]),
            'left': Line(corners[0], corners[2]),
            'right': Line(corners[1], corners[3]),
            'rear': Line(corners[3], corners[2]),
            'cog': calc_cog(corners),
            'area': calc_area(corners),
            'pins': None
        }


class Line:
    def __init__(self, p1: Tuple[float, float], p2: Tuple[float, float]):
        self.p1, self.p2 = p1, p2
        self.vertical = p1[0] == p2[0]
        self.horizontal = p1[1] == p2[1]

When I type type the following,

rec1 = Rectangle(rec1_corners, show=True, name='Nr1')
rec1.sides['f...

Pycharm will suggest 'front' for me. Better still, when I do

rec1.sides['front'].ver...

Pycharm will suggest .vertical

So Pycharm remembers the keys from the dictionary literal declaration in the class's __init__, and also their values' expected types. Or rather: it expects any value to have any one of the types that are in the literal declaration - probably the same as if I had done a self.elements = {} # type: Union[type1, type2] would do. Either way, I find it super helpful.

If your functions have their outputs type-hinted, then Pycharm will also take that into account.

So assuming that in the Rectangle example above, I wanted to indicate that pins is a list of Pin objects... if pins was a normal class attribute, it would be:

    self.pins = None  # type: List[Pin]

(provided the necessary imports were done)

Is there a way to give the same type hint in the dictionary literal declaration?

The following does not achieve what I am looking for:

Add a Union[...] type hint at the end of the literal declaration?

            'area': calc_area(corners),
            'pins': None
        }  # type: Union[Line, Tuple[float, float], float, List[Pin]]

Adding a type-hint to every line:

            'area': calc_area(corners),  # type: float
            'pins': None  # type: List[Pin]
        }

Is there a best practice for this kind of thing?


Some more background:

I work with Python in PyCharm and I make extensive use of typing, since it helps me to predict and validate my work as I go along. When I create new classes, I also sometimes throw some less frequently used properties into a dictionary to avoid cluttering the object with too many attributes (this is helpful in debug mode).

like image 476
levraininjaneer Avatar asked Jun 25 '18 21:06

levraininjaneer


People also ask

Can dictionary values have different types?

A dictionary value can be any type of object Python supports, including mutable types like lists and dictionaries, and user-defined objects, which you will learn about in upcoming tutorials.

Can dictionary have mixed data types?

One can only put one type of object into a dictionary. If one wants to put a variety of types of data into the same dictionary, e.g. for configuration information or other common data stores, the superclass of all possible held data types must be used to define the dictionary.

Can a dictionary contain items of different types Python?

The keys are immutable. Just like lists, the values of dictionaries can hold heterogeneous data i.e., integers, floats, strings, NaN, Booleans, lists, arrays, and even nested dictionaries. This article will provide you a clear understanding and enable you to work proficiently with Python dictionaries.

Can keys in dictionary have different data types?

For example, you can use an integer, float, string, or Boolean as a dictionary key. However, neither a list nor another dictionary can serve as a dictionary key, because lists and dictionaries are mutable. Values, on the other hand, can be any type and can be used more than once.


1 Answers

You are looking for TypedDict. It is currently only a mypy-only extension, but there are plans to make it an officially sanctioned type in the near-future. I am not sure if PyCharm supports this feature yet, though.

So, in your case, you'd do:

from mypy_extensions import TypedDict

RectangleElements = TypedDict('RectangleElements', {
    'front': Line,
    'left': Line,
    'right': Line,
    'rear': Line,
    'cog': float,
    'area': float,
    'pins': Optional[List[Pin]]
})

class Rectangle:
    def __init__(self, corners: Tuple[Tuple[float, float]], **kwargs):
        self.x, self.z = corners[0][0], corners[0][1]
        self.elements = {
            'front': Line(corners[0], corners[1]),
            'left': Line(corners[0], corners[2]),
            'right': Line(corners[1], corners[3]),
            'rear': Line(corners[3], corners[2]),
            'cog': calc_cog(corners),
            'area': calc_area(corners),
            'pins': None
        }  # type: RectangleElements

If you are using Python 3.6+, you can type this all more elegantly using the class-based syntax.

In your specific case though, I think most people would just store those pieces of data as regular fields instead of a dict. I'm sure you've already thought through the pros and cons of that approach though, so I'll skip lecturing you about it.

like image 186
Michael0x2a Avatar answered Oct 18 '22 04:10

Michael0x2a