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?
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.
Tuple[int, ...] as per PEP484typing.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 metadatax, it should ignore it and simply treat the type asT.
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).
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]]typing.Length - does that actually exists, or are you just expressing desire for a new feature?ValueRange used in the documentation.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.
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.Tuple[(int,)*3] is equivalent to Tuple[int, int, int]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!
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.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.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