How to type a Tuple with many elements in Python?

Viewed 243

I am experimenting with the typing module and I wanted to know how to properly type something like a Nonagon (a 9 point polygon), which should be a Tuple and not a List because it should be immutable.

In 2D space, it would be something like this:

Point2D = Tuple[float, float]
Nonagon = Tuple[Point2D, Point2D, Point2D, Point2D, Point2D, Point2D, Point2D, Point2D, Point2D]

nine_points: Nonagon = (
    (0.0, 0.0),
    (6.0, 0.0),
    (6.0, 2.0),
    (2.0, 2.0),
    (6.0, 5.0),
    (2.0, 8.0),
    (6.0, 8.0),
    (6.0, 10.0),
    (0.0, 10.0),
)

Is there any syntactic sugar available to make the Nonagon declaration shorter or easier to read?

This is not valid Python, but I am looking for something similar to this:

Nonagon = Tuple[*([Point2D] * 9)]  # Not valid Python

Or using NamedTuple

# Not properly detected by static type analysers
Nonagon = NamedTuple('Nonagon', [(f"point_{i}", Point2D) for i in range(9)])  

This is NOT what I want:

# Valid but allows for more and less than 9 points
Nonagon = Tuple[Point2D, ...]  

I think the most adequate way would be something like:

from typing import Annotated

# Valid but needs MinMaxLen and checking logic to be defined from scratch
Nonagon = Annotated[Point2D, MinMaxLen(9, 9)]  
3 Answers

The very first way you described it:

Nonagon = Tuple[Point2D, Point2D, Point2D, Point2D, Point2D, Point2D, Point2D, Point2D, Point2D]

is the right way to do it, as discussed in a 2016 python/typing issue. Yes, it's a bit ugly, but you only have to define it once.

You could leverage Annotated here, but it ultimately depends on whether your typechecker can make use of the annotations that you include. Not all typecheckers would necessarily make use of those annotations, whereas spelling out how many elements should be in the tuple is something that every (functioning) typechecker should recognize.

You can use the types module. All type hints come from types.GenericAlias.

From the doc:

Represent a PEP 585 generic type
E.g. for t = list[int], t.__origin__ is list and t.__args__ is (int,).

This means that you can make your own type hinting by passing the type arguments to the class itself.

>>> Point2D = tuple[float, float]
>>> Nonagon = types.GenericAlias(tuple, (Point2D,)*9)
>>> Nonagon
tuple[tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float]]

Not sure if this would work for your purposes, but numpy might be useful here.


import numpy as np
ones = np.ones(9)
nonogon = ones * Point2D

Related