Specify length of Sequence or List with Python typing module

20,907

Solution 1

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.

Solution 2

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.

Solution 3

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

Annotated[List[float], 3]

Solution 4

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!

Share:
20,907
Admin
Author by

Admin

Updated on October 07, 2021

Comments

  • Admin
    Admin over 2 years

    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.

  • Admin
    Admin almost 7 years
    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)
  • Rick
    Rick over 6 years
    "so far..." Are there any plans for specifying a fixed-length, mutable sequence Generic in the typing module at some point?
  • Tomasz Bartkowiak
    Tomasz Bartkowiak almost 5 years
    If you're using tuple you can also use literal ellipsis, i.e. Tuple[int, ...] as per PEP484
  • Martijn Pieters
    Martijn Pieters almost 5 years
    @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.
  • Matt
    Matt almost 4 years
    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.
  • Martijn Pieters
    Martijn Pieters almost 4 years
    @Matt: sure, but there is no built-in Python type that lets you do that so no type hints either.
  • balu
    balu about 3 years
    FYI @MartijnPieters' first suggestion can be abbreviated as Tuple[10 * (float, )], which I find rather short and neat, as it expresses its purpose very clearly.
  • Timmmm
    Timmmm about 3 years
    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.
  • Martijn Pieters
    Martijn Pieters about 3 years
    @Timmmm: That's because JavaScript doesn't have tuples, so Typescript had to invent them from whole cloth.
  • Timmmm
    Timmmm about 3 years
    That is the original reason, yes. It isn't an argument for not supporting that feature though.
  • binaryfunt
    binaryfunt about 3 years
    @balu Are you sure you can do Tuple[10 * (float, )]? That isn't allowed in Python 3.7 at least
  • balu
    balu about 3 years
    @binaryfunt I don't have access to Python 3.7 right now but it works for me on both Python 3.6.13 and Python 3.8.5, so I guess it should be working on Python 3.7, too. What is the error you're getting?
  • binaryfunt
    binaryfunt about 3 years
    @balu Invalid type comment or annotation [valid-type]. I get that for the declaration a: Tuple[10 * (float, )]. mypy 0.812, python 3.7.0 (GCC 7.3.0)
  • balu
    balu about 3 years
    @binaryfunt Sounds like a Mypy issue, not a Python one. Maybe you should take this up with the Mypy developers?
  • Daniel
    Daniel over 2 years
    It's not possible with a list, but I would like to point out that it does make sense, for the same reasons that TypedDict exists - schema/validation libraries.
  • Nulano
    Nulano over 2 years
    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.
  • Alex
    Alex over 2 years
    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.
  • pabouk - Ukraine stay strong
    pabouk - Ukraine stay strong about 2 years
    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]]