As of May 2015, PEP 484 (Type Hints) has been formally accepted. The draft implementation is also available at github under ambv/typehinting.

In September 2015, Python 3.5 was released with support for Type Hints and includes a new typing module. This allows for the specification of types contained within collections. As of November 2015, JetBrains PyCharm 5.0 fully supports Python 3.5 to include Type Hints as illustrated below.

from typing import List

def do_something(l: List[str]):
    for s in l:
        s  # str

Original Answer

As of Aug 2014, I have confirmed that it is not possible to use Python 3 type annotations to specify types within collections (ex: a list of strings).

The use of formatted docstrings such as reStructuredText or Sphinx are viable alternatives and supported by various IDEs.

It also appears that Guido is mulling over the idea of extending type annotations in the spirit of mypy: http://mail.python.org/pipermail/python-ideas/2014-August/028618.html

Answer from Eric W. on Stack Overflow
๐ŸŒ
Python documentation
docs.python.org โ€บ 3 โ€บ library โ€บ typing.html
typing โ€” Support for type hints
1 week ago - Type variable tuples can be happily combined with normal type variables: class Array[DType, *Shape]: # This is fine pass class Array2[*Shape, DType]: # This would also be fine pass class Height: ... class Width: ...
Top answer
1 of 5
346

As of May 2015, PEP 484 (Type Hints) has been formally accepted. The draft implementation is also available at github under ambv/typehinting.

In September 2015, Python 3.5 was released with support for Type Hints and includes a new typing module. This allows for the specification of types contained within collections. As of November 2015, JetBrains PyCharm 5.0 fully supports Python 3.5 to include Type Hints as illustrated below.

from typing import List

def do_something(l: List[str]):
    for s in l:
        s  # str

Original Answer

As of Aug 2014, I have confirmed that it is not possible to use Python 3 type annotations to specify types within collections (ex: a list of strings).

The use of formatted docstrings such as reStructuredText or Sphinx are viable alternatives and supported by various IDEs.

It also appears that Guido is mulling over the idea of extending type annotations in the spirit of mypy: http://mail.python.org/pipermail/python-ideas/2014-August/028618.html

2 of 5
228

As of Python 3.9, builtin types are generic with respect to type annotations (see PEP 585). This allows to directly specify the type of elements:

def my_func(l: list[int]):
    pass

This also extends to most other container types of the standard library, for example collections.deque or collections.abc.Mapping.


Various tools may support this syntax earlier than Python 3.9. When annotations are not inspected at runtime, the syntax is valid using quoting or __future__.annotations.

# quoted
def my_func(l: 'list[int]'):
    pass
# postponed evaluation of annotation
from __future__ import annotations

def my_func(l: list[int]):
    pass

As a consequence of PEP 585, most helpers in typing corresponding to standard library types are deprecated, such as typing.List, typing.Deque or typing.Mapping. They should only be used if compatibility with Python versions prior to 3.9 is required.

Discussions

What's the appropriate type-hint for a function that can accept a list of floats OR a numpy array?
If your function accepts an array-like input that you intend to treat as an array, I think the "correct" thing to do is to run that input through one of the numpy.as*array() or numpy.atleast_*() functions before you use it. In both your examples, I think numpy.atleast_1d would probably be sensible. By the way, numpy does have 0-D arrays, so something like this can pass static checking and then fail at runtime if you don't run it through atleast_1d: import numpy as np import numpy.typing as npt myvar: npt.NDArray = np.array(1) print(myvar[0]) # or even print(len(myvar)) The type hints aren't enough on their own. More on reddit.com
๐ŸŒ r/learnpython
3
3
July 11, 2023
Tutorial on type hinting any matrix with Protocol (compatible with numpy and nested lists)
Cool post. This is besides the aim of the post, but there is a minor bug in section 2, In particular, class FixedShapeMatrix: def __init__(rows: int, cols: int) -> None: _matrix = [[0]*cols]*rows The [list]*int construct will create an list containing n references to the same list. More on reddit.com
๐ŸŒ r/Python
13
11
October 3, 2024
Type-hint a numpy NDArray with different possible dtypes
How should I type-hint a numpy ndarray which can be of any integer type? My thoughts are: from numpy.typing import DTypeLike, NDArray # option (1) arr: NDArray[+(np.int8, np.int16, np.int32, np.int64)] # option (2) ScalarIntType: tuple[DTypeLike, ...] = (np.int8, np.int16, np.int32, np.int64) ... More on discuss.python.org
๐ŸŒ discuss.python.org
0
February 27, 2024
Type-hinting Numpy functions to accept both arrays and scalars, pedantically
Consider the following function: import numpy as np def law_of_cosines(side1, side2, angle): return np.sqrt( np.square(side1) + np.square(side2) - 2 * side1 * side2 * np.cos(angle) ) This function can accept more or less arbitrary combinations of Numpy arrays of float-based dtypes, โ€œarray-likeโ€ ... More on discuss.python.org
๐ŸŒ discuss.python.org
0
April 18, 2023
๐ŸŒ
Mypy
mypy.readthedocs.io โ€บ en โ€บ stable โ€บ cheat_sheet_py3.html
Type hints cheat sheet - mypy 1.19.1 documentation
# For most types, just use the name of the type in the annotation # Note that mypy can usually infer the type of a variable from its value, # so technically these annotations are redundant x: int = 1 x: float = 1.0 x: bool = True x: str = "test" x: bytes = b"test" # For collections on Python 3.9+, the type of the collection item is in brackets x: list[int] = [1] x: set[int] = {6, 7} # For mappings, we need the types of both keys and values x: dict[str, float] = {"field": 2.0} # Python 3.9+ # For tuples of fixed size, we specify the types of all the elements x: tuple[int, str, float] = (3, "yes
๐ŸŒ
Medium
medium.com โ€บ data-science-collective โ€บ do-more-with-numpy-array-type-hints-annotate-validate-shape-dtype-09f81c496746
Do More with NumPy Array Type Hints: Annotate & Validate Shape & Dtype | by Christopher Ariza | Data Science Collective | Medium
May 26, 2025 - It might be a one-dimensional (1D) array of Booleans, or a three-dimensional (3D) array of 8-bit unsigned integers. As the built-in function isinstance() will show, every array is an instance of np.ndarray, regardless of shape or the type of elements stored in the array, i.e., the dtype.
๐ŸŒ
Scipy
proceedings.scipy.org โ€บ articles โ€บ WPXM6451
Improving Code Quality with Array and DataFrame Type Hints - SciPy Proceedings
July 10, 2024 - This article demonstrates practical approaches to fully type-hinting generic NumPy arrays and StaticFrame DataFrames, and shows how the same annotations can improve code quality with both static analysis and runtime validation. ... As tools for Python type annotations (or hints) have evolved, ...
๐ŸŒ
Python Like You Mean It
pythonlikeyoumeanit.com โ€บ Module5_OddsAndEnds โ€บ Writing_Good_Code.html
Writing Good Code โ€” Python Like You Mean It
For example, numpy.ndarray hints that a variable should be passed an instance of the nd-array class. You should strive to write type-hints that pass the duck test: if your function is expecting a duck then hint for something that walks like a duck, quacks like a duck, etc. This will help you avoid writing type-hints that are overly narrow, and which are ultimately non-Pythonic in their strictness.
Find elsewhere
๐ŸŒ
NumPy
numpy.org โ€บ devdocs โ€บ reference โ€บ typing.html
Typing (numpy.typing) โ€” NumPy v2.5.dev0 Manual
The dtype of numpy.recarray, and the Creating record arrays functions in general, can be specified in one of two ways: Directly via the dtype argument. With up to five helper arguments that operate via numpy.rec.format_parser: formats, names, titles, aligned and byteorder. These two approaches are currently typed as being mutually exclusive, i.e.
๐ŸŒ
Dagster
dagster.io โ€บ blog โ€บ python-type-hinting
Using Type Hinting in Python Projects
However, this dynamic nature can ... as a part of the standard library via PEP 484, allow you to specify the expected type of a variable, function parameter, or return value....
๐ŸŒ
Jack Atkinson
jackatkinson.net โ€บ post โ€บ numpy_typing
Typing in numpy - Jack Atkinson's Website
April 27, 2025 - If this is important perhaps use NDArray[np.dtype] internally instead, and build in dtype checks with Raises in user-facing code e.g. for passing an array of strings to something that needs numbers. Bear in mind that typing in Python is optional and static, so should not be treated (IMO) quite the same way as for a checked compiled language. Indeed, the flexibility of Python types can be regarded as a โ€˜featureโ€™.
๐ŸŒ
Python
peps.python.org โ€บ pep-0484
PEP 484 โ€“ Type Hints | peps.python.org
It is allowable to use string literals as part of a type hint, for example: class Tree: ... def leaves(self) -> List['Tree']: ... A common use for forward references is when e.g. Django models are needed in the signatures. Typically, each model is in a separate file, and has methods taking arguments whose type involves other models.
๐ŸŒ
FastAPI
fastapi.tiangolo.com โ€บ python-types
Python Types Intro - FastAPI
These "type hints" or annotations are a special syntax that allow declaring the type of a variable. By declaring types for your variables, editors and tools can give you better support.
๐ŸŒ
Reddit
reddit.com โ€บ r/python โ€บ tutorial on type hinting any matrix with protocol (compatible with numpy and nested lists)
r/Python on Reddit: Tutorial on type hinting any matrix with Protocol (compatible with numpy and nested lists)
October 3, 2024 -

I went down a rabbit hole trying to find the perfect way to type hint a matrix. Here's what I learned. First, the naive approach:

matrix3x3: list[list[int]] = [[1,2,3],[4,5,6],[7,8,9]]

There are two problems with this. The first is that list[list[int]] is a concrete type, and we'd like it to be abstract. As is, mypy would raise an error if we tried to do this:

import numpy as np
matrix3x3 = np.ndarray(shape=(3, 3), buffer=np.array([[1,2,3],[4,5,6],[7,8,9]])) # error

We would like to be able to do this though, because an NDArray shares all the relevant qualities of a matrix for our application.

The second problem is more subtle. matrix3x3 is meant to always be 3x3, but Python's lists are dynamically resizable, which means the shape can be tampered with. Ideally, we'd like mypy to be able to raise an error before runtime if someone else later tries to write matrix3x3.pop() or matrix3x3[0].append(something). This is not a problem in a language like Java, where Arrays are fixed-size.

There are three ways around these issues:

1. Switch to a statically-typed language.

This is the least preferable option, but something everyone should consider if they keep resisting duck typing. I still prefer duck typing at least for prototyping.

2. Modify the implementation.

This is certainly better, but not the best option. It's worth demonstrating how you could do this. For example, we can start with this:

class FixedShapeMatrix:
  def __init__(rows: int, cols: int) -> None:
    _matrix = [[0 for c in cols] for r in rows]

and continue defining the functionality of the FixedShapeMatrix object so that it has an immutable shape with mutable entries.

Another example is to just use numpy instead:

import numpy as np
from numpy import typing as npt

matrix3x3: npt.NDArray[np.int64] = np.ndarray((3,3), buffer=np.array([[1,2,3],[4,5,6],[7,8,9]])

Both of these solutions suffer from the same problem: they require significant refactoring of the existing project. And even if you had the time, you will lose generality when you pick the NDArray or FixedShapeMatrix implementations. Ideally, you want matrix3x3 to be structurally typed such that any of these implementations can be assigned to it. When you pigeonhole your matrix3x3 type, you lose the Abstraction of OOP. Thankfully, with Protocol, there's another way.

3. Structural subtyping.

Note: I'm going to be using Python 3.12 typing notation. As a quick reference, this is code in 3.11:

from typing import TypeVar, Generic

T = TypeVar('T', bound=int|float)


class MyClass(Generic[T]):

  def Duplicates(self, val: T) -> list[T]:
    return [val] * 2

And this is the same code in 3.12 (no imports needed):

class MyClass[T: int|float]:

  def Duplicates(self, val: T) -> list[T]:
    return [val] * 2

So, let's finally try to make an abstract matrix type directly. I'm going to show you how I iteratively figured it out. If you're already a little familiar with Protocol, you might have guessed this:

type Matrix[T] = Sequence[Sequence[T]]

But the problem is that Sequence is read-only. We're going to have to create our own type from scratch. The best way to start is to realize which methods we really need from the matrix:

  1. indexing (read + write)

  2. iterable

  3. sized

The first attempt might be this:

from typing import Protocol


class Matrix(Protocol):

  def __getitem__(): ...

  def __setitem__(): ...

  def __len__(): ...

  def __iter__(): ...

But there are multiple problems with this. The first is that we need to explicitly annotate the types of each of these functions, or else our matrix won't be statically hinted.

from typing import Protocol, Iterator


class Matrix(Protocol):

  def __getitem__(self, index: int) -> int | Matrix: ...

  def __setitem__(self, index: int, val: int | Matrix) -> None: ...

  def __len__(self) -> int: ...

  def __iter__(self) -> Iterator[int | Matrix]: ...

The idea here is that matrix3x3[0][0] is an int, while the type of matrix3x3[0] is recursively a matrix that contains ints. But this doesn't protect against matrix3x3: Matrix = [1,2,3,[4,5,6],7,8,9] , which is not a matrix.

Here we realize that we should handle the internal rows as their own type.

from typing import Protocol, Iterator


class MatrixRow(Protocol):

  def __getitem__(self, index: int) -> int: ...

  def __setitem__(self, index: int, value: int) -> None: ...

  def __len__(self) -> int: ...

  def __iter__(self) -> Iterator[int]: ...


class Matrix(Protocol):

  def __getitem__(self, index: int) -> MatrixRow: ...

  def __setitem__(self, index: int, value: MatrixRow) -> None: ...

  def __len__(self) -> int: ...

  def __iter__(self) -> Iterator[MatrixRow]: ...

Now both the matrix and its rows are iterable, sized, and have accessible and mutable indexes.

matrix3x3: Matrix = [[1,2,3],[4,5,6],[7,8,9]] # good
matrix3x3.append([10,11,12]) # error - good!
matrix3x3[0][2] = 10 # good
matrix3x3[0][0] += 1 # good
matrix3x3[1].append(7) # error - good!

There's just one bug though. See if you can find it first:

matrix3x3[1] = [4,5,6,7] # no error - bad!

The solution is we need to remove __setitem__ from Matrix. We will still be able to modify the elements of any MatrixRow without it. Bonus points if you understand why (hint: references).

So let's go ahead and do that, and as a final touch, let's make it so that the matrix values all must have the same type. To do this, we enforce a generic type that supports integer operations (int, float, np.int32, np.float64, etc). Here's how I did that:

from typing import Protocol, Iterator, SupportsInt


class MatrixRow[T: SupportsInt](Protocol):

  def __getitem__(self, index: int) -> T: ...

  def __setitem__(self, index: int, value: T) -> None: ...

  def __len__(self) -> int: ...

  def __iter__(self) -> Iterator[T]: ...


class Matrix[S: SupportsInt](Protocol):

  def __getitem__(self, index: int) -> MatrixRow[S]: ...

  def __len__(self) -> int: ...

  def __iter__(self) -> Iterator[MatrixRow[S]]: ...

Now all of these work!

matrix3x3: Matrix[int]
matrix3x3 = [[1,2,3],[4,5,6],[7,8,9]]
matrix3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]])
matrix3x3 = np.ndarray(shape=(3, 3), buffer=np.array([[1,2,3],[4,5,6],[7,8,9]]))
for row in matrix3x3:
  for val in row:
    print(val)
print(len(matrix3x3), len(matrix3x3[0]))

And all of these raise errors!

matrix3x3.append([10,11,12])
matrix3x3[2].append(10)
matrix3x3.pop()
matrix3x3[0].pop()
matrix3x3[0][0] = "one"

And even if some of those implementations are intrinsically mutable in size, our type system lets mypy catch any bug where matrix3x3 is reshaped. Unfortunately, there's no way to prevent someone from assigning a 4x7 matrix to matrix3x3, but at least it's named clearly. Maybe someday there will be Python support for fixed-size lists as types.

๐ŸŒ
Towards Data Science
towardsdatascience.com โ€บ home โ€บ latest โ€บ improving code quality with array and dataframe type hints
Improving Code Quality with Array and DataFrame Type Hints | Towards Data Science
January 13, 2025 - This article demonstrates practical approaches to fully type-hinting arrays and DataFrames, and shows how the same annotations can improve code quality with both static analysis and runtime validation. StaticFrame is an open-source DataFrame library of which I am an author. Type hints (see PEP 484) improve code quality in a number of ways. Instead of using variable names or comments to communicate types, Python-object-based type annotations provide maintainable and expressive tools for type specification.
๐ŸŒ
Medium
medium.com โ€บ data-science โ€บ improving-code-quality-with-array-and-dataframe-type-hints-cac0fb75cc11
Improving Code Quality with Array and DataFrame Type Hints | by Christopher Ariza | TDS Archive | Medium
September 19, 2024 - This article demonstrates practical approaches to fully type-hinting arrays and DataFrames, and shows how the same annotations can improve code quality with both static analysis and runtime validation. StaticFrame is an open-source DataFrame library of which I am an author. Type hints (see PEP 484) improve code quality in a number of ways. Instead of using variable names or comments to communicate types, Python-object-based type annotations provide maintainable and expressive tools for type specification.
๐ŸŒ
Like Geeks
likegeeks.com โ€บ home โ€บ python โ€บ python type hinting for numpy arrays
Python Type Hinting for NumPy Arrays
Learn Python type hinting for NumPy arrays with examples. Cover dtypes, shapes, sizes, and using typing tools like Union and Literal for clarity.
๐ŸŒ
Python.org
discuss.python.org โ€บ python help
Type-hint a numpy NDArray with different possible dtypes - Python Help - Discussions on Python.org
February 27, 2024 - How should I type-hint a numpy ndarray which can be of any integer type? My thoughts are: from numpy.typing import DTypeLike, NDArray # option (1) arr: NDArray[+(np.int8, np.int16, np.int32, np.int64)] # option (2) ScalarIntType: tuple[DTypeLike, ...] = (np.int8, np.int16, np.int32, np.int64) ...
๐ŸŒ
Python.org
discuss.python.org โ€บ python help
Type-hinting Numpy functions to accept both arrays and scalars, pedantically - Python Help - Discussions on Python.org
April 18, 2023 - Consider the following function: import numpy as np def law_of_cosines(side1, side2, angle): return np.sqrt( np.square(side1) + np.square(side2) - 2 * side1 * side2 * np.cos(angle) ) This function can accept more or less arbitrary combinations ...
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ python โ€บ type-hints-in-python
Type Hints in Python - GeeksforGeeks
May 3, 2025 - Type hints are not enforced by the Python interpreter; they are purely for static analysis by external tools like mypy. So, even with type hints, the code will still run with errors unless a static type checker is used.
๐ŸŒ
w3resource
w3resource.com โ€บ numpy โ€บ snippet โ€บ exploring-numpy-typing.php
Exploring numpy.typing for Enhanced Type Hints
Here, NDArray[np.float64] ensures the function accepts only NumPy arrays of type float64. The type hints prevent passing incompatible types.