83

I'm giving the Python typing module a shot.

I know that it's valid to specify the length of a List like the following*:

List[float, float, float]   # List of 3 floats <-- NOTE: this is not valid Python

Is there any shorthand for longer lists? What if I want to set it to 10 floats?

List[float * 10]   # This doesn't work.

Any idea if this is possible, this would be handy.


*NOTE: It turns out that supplying multiple arguments to Sequence[] (and its subclasses) in this manner is currently NOT valid Python. Furthermore, it is currently not possible to specify a Sequence length using the typing module in this way.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
John Brodie
  • 971
  • 1
  • 6
  • 5

4 Answers4

62

You can't. A list is a mutable, variable length structure. If you need a fixed-length structure, use a tuple instead:

Tuple[float, float, float, float, float, float, float, float, float, float]

Or better still, use a named tuple, which has both indices and named attributes:

class BunchOfFloats(NamedTuple):
    foo: float
    bar: float
    baz: float
    spam: float
    ham: float
    eggs: float
    monty: float
    python: float
    idle: float
    cleese: float

A list is simply the wrong data type for a fixed-length data structure.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 4
    If you're using tuple you can also use literal ellipsis, i.e. `Tuple[int, ...]` as per [PEP484](https://docs.python.org/3/library/typing.html#typing.Tuple) – Tomasz Bartkowiak Jul 23 '19 at 10:03
  • 19
    @TomaszBartkowiak: that's the *opposite* of what is being asked. Yes, you can declare a tuple of variable length containing a single type that way. But that's *not a fixed size*. – Martijn Pieters Jul 23 '19 at 11:16
  • 17
    Sometimes you want a mutable container that is of fixed length. E.g. if you want to init the container items to None, but then update the items with new values. But the container would still remain fixed in size. – Matt Jul 23 '20 at 00:55
  • 5
    @Matt: sure, but there is no built-in Python type that lets you do that so no type hints either. – Martijn Pieters Jul 25 '20 at 15:52
  • 1
    Typescript allows you to do this (i.e. hint arrays as if they were tuples) and it works fine. In practice this is really annoying because Python users often *do* use lists as if they were tuples, and I would really like to be able to add type hints to them. – Timmmm May 10 '21 at 20:13
  • @Timmmm: That's because JavaScript **doesn't have tuples**, so Typescript had to invent them from whole cloth. – Martijn Pieters May 16 '21 at 16:05
36

typing.Annotated can be handy here. It allows you to specify arbitrary metadata to type hints:

Annotated[list[float], 3]

New to Annotated? Here's a snippet from the docs:

If a library (or tool) encounters a typehint Annotated[T, x] and has no special logic for metadata x, it should ignore it and simply treat the type as T.

Notably, mypy has an outstanding request (open as of November 2022) for something like this. In the meantime, think of Annotated as for developer readability, not for automated inspection (unless you develop inspection tooling).

Intrastellar Explorer
  • 3,005
  • 9
  • 52
  • 119
Zaffy
  • 16,801
  • 8
  • 50
  • 77
  • 6
    Thanks, it is good to know that `typing.Annotated` exists. --- IMHO the answer should explain that the added integer `3` works just like a comment attached to the annotated variable. You have to write your own tools to actually use the additional annotation. --- Besides that, adding just an integer number alone will make the additional annotation ambiguous. It would be better to create a structure to be able to express the context - something like: `Annotated[List[float], Length[3]]` – pabouk - Ukraine stay strong May 16 '22 at 15:01
  • 1
    I'm not seeing `typing.Length` - does that actually exists, or are you just expressing desire for a new feature? – Addison Klinke Jul 27 '22 at 15:25
12

So far, only tuples support specifying a fixed number of fields and it has no short-cut for a fixed number of repetitions.

Here's the definition and docstring from the typing module:

class Tuple(tuple, extra=tuple, metaclass=TupleMeta):
    """Tuple type; Tuple[X, Y] is the cross-product type of X and Y.

    Example: Tuple[T1, T2] is a tuple of two elements corresponding
    to type variables T1 and T2.  Tuple[int, float, str] is a tuple
    of an int, a float and a string.

    To specify a variable-length tuple of homogeneous type, use Tuple[T, ...].
    """

    __slots__ = ()

    def __new__(cls, *args, **kwds):
        if _geqv(cls, Tuple):
            raise TypeError("Type Tuple cannot be instantiated; "
                            "use tuple() instead")
        return _generic_new(tuple, cls, *args, **kwds)

Since lists are a mutable, variable-length type, it doesn't make any sense to use a type declaration to specify a fixed size.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • Thanks Raymond, clear enough. While both answers I've received on here are accurate and clarifying, I'm still not 100% sure on the best way to hint for functions that really need set length Sequence input. I suppose that just putting this in the docstring isn't too bad, but that seems like a shame. (I'm really enjoying how PyCharm picks up on these hints in the generated help for each method) – John Brodie Aug 07 '17 at 15:39
  • 2
    "so far..." Are there any plans for specifying a fixed-length, mutable sequence `Generic` in the `typing` module at some point? – Rick Feb 05 '18 at 16:35
  • 2
    `Since lists are a mutable, variable-length type, it doesn't make any sense to use a type declaration to specify a fixed size.` One may want to keep a list of fixed size just for mutability (i.e. to be able to update elements in-place). That would make perfect sense IMO. A simple example: a list representing a range `[start, stop]` where bounds are updatable. – Eugene Yarmash Feb 06 '23 at 15:07
6

When also confronted with the same problem, I was not happy seeing Martijn Pieters answer. Since I wanted a "fast" and "easy" way to solve this problem.

So I tried the other suggestions listed here first.

Note: I used VSCode with Pylance as Language Server

Zaffys answer was my favorite

def demystify(mystery: Annotated[Tuple[int], 6]):
    a, b, c, d, e, f = mystery
    print(a, b, c, d, e, f)

Hint for the function then looks like this: demystify: (mystery: Tuple[int]) -> None Also I get a Pylance Error Tuple size mismatch: expected 6 but received for the line a, b, c, d, e, f = mystery

Next I tried Tuple[6 * (int, )] which was mentioned by balu in the comments of Martijn Pieters answer

def demystify(mystery: Tuple[6 * (int,)]):
    a, b, c, e, f, g = mystery
    print(a, b, c, e, f, g)

Resulting in the same Pylance Error as before. Hint for the function was this: demystify: (mystery: Tuple[Tuple[Type[int], ...]]) -> None

Going back to writing down the expected length:

def demystify(mystery: Tuple[int, int, int, int, int, int]):
    a, b, c, e, f, g = mystery
    print(a, b, c, e, f, g)

This resolved the Pylance Error, and got me a "clear" function hint: demystify: (mystery: Tuple[int, int, int, int, int, int]) -> None

But just like John Brodie, I was not happy with this solution.

Now back to the, at first, unwanted answer:

class MysteryType(NamedTuple):
    a: int
    b: int
    c: int
    d: int
    e: int
    f: int
    g: int

def demystify(mystery: MysteryType):
    print(*mystery)

The function hint now seems more mystic: demystify: (mystery: MysteryType) -> None but creating a new MysteryType gives me all the information I need: (a: int, b: int, c: int, d: int, e: int, f: int, g: int)

Also I can use the MysteryType in other methods and functions without the need of counting the type hints.

So, to make a long story short and paraphrase the Zen of Python:

NamedTuples are one honking great idea -- let's do more of those!

Alex
  • 185
  • 2
  • 5
  • 3
    `Annotated[Tuple[int], 6]` means a tuple with a single int (with a 6 as metadata). Zaffy's answer is `Annotated[List[int], 6]` which is an arbitrary list of ints (with a 6 as metadata). Ideally a type checker would be able to read the 6 to understand that you want a fixed-sized list, but this is not a standard way to specify it. – Nulano Mar 19 '22 at 21:31
  • Thanks for pointing this out. When using `Annotated[List[int], 6]` there will - of course - no error be shown. Still I don't get proper type hints in VSCode with Pylance as Language Server. So I would still stick with the NamedTuple solution. Yet the `Annotated[List[int], 6]` might work well in other code editors. – Alex Mar 22 '22 at 12:57