108

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

List[float, float, float]  # List of 3 floats

or:

List[float * 10]  # List of 10 floats

Is this possible?

0

5 Answers 5

74

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.

Sign up to request clarification or add additional context in comments.

4 Comments

If you're using tuple you can also use literal ellipsis, i.e. Tuple[int, ...] as per PEP484
@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.
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: sure, but there is no built-in Python type that lets you do that so no type hints either.
62

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).

6 Comments

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]]
I'm not seeing typing.Length - does that actually exists, or are you just expressing desire for a new feature?
@AddisonKlinke it is a custom class like the ValueRange used in the documentation.
Is there an alternative to this for Python < 3.9 ?
@EbramShehata I doubt so, but since it is pretty dumb annotation, you can easily create a stub assuming python 3.3 supports the subscript operator.
|
14

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.

5 Comments

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)
"so far..." Are there any plans for specifying a fixed-length, mutable sequence Generic in the typing module at some point?
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.
There is actually a shortcut for a fixed number of repetitions: Tuple[(int,)*3] is equivalent to Tuple[int, int, int]
@Retr0id I just tried that and mypy gives, "error: Invalid type comment or annotation". That said, CPython doesn't complain.
8

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!

2 Comments

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.
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.
0

This answer won't help with type hints (see this answer above), but it will help with type checking via the pydantic package:

from typing import Any, TypeVar, Tuple
from collections.abc import Sequence
from annotated_types import Len
from typing import Annotated
from pydantic import BaseModel

SequenceType = TypeVar('SequenceType', bound=Sequence[Any])
ShortSequence = Annotated[SequenceType, Len(max_length=2)]

class Test(BaseModel):
    test: ShortSequence

print("successful!")
Test(test=[1,2])
print("fail:")
Test(test=[1,2,3])

Output:

successful!
fail:
>Traceback
ValidationError: 1 validation error for Test
test
  Value should have at most 2 items after validation, not 3 [type=too_long, input_value=[1, 2, 3], input_type=list]
    For further information visit https://errors.pydantic.dev/2.9/v/too_long

Related pydantic docs: https://docs.pydantic.dev/latest/concepts/types/#generics

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.