Good on you for using type annotations! As the documentations says, if you are on Python 3.9+, you should most likely never use typing.Sequence due to its deprecation. Since the introduction of generic alias types in 3.9 the collections.abc classes all support subscripting and should be recognized correctly by static type checkers of all flavors.

So the benefit of using collections.abc.T over typing.T is mainly that the latter is deprecated and should not be used.

As mentioned by jsbueno in his answer, annotations will have no runtime implications either way, unless of course they are explicitly picked up by a piece of code (see my other answer here). They are just an essential part of good coding style. But your function would still work, i.e. your script would execute without error, even if you annotated your function with something absurd like def average(sequence: 4%3): ....


Proper annotations are still extremely valuable. Thus, I would recommend you get used to some of the best practices as soon as possible. (A more-or-less strict static type checker like mypy is very helpful for that.) For one thing, when you are using generic types like Sequence, you should always provide the appropriate type arguments. Those may be type variables, if your function is also generic or they may be concrete types, but you should always include them.

In your case, assuming you expect the contents of your sequence to be something that can be added with the same type and divided by an integer, you might want to e.g. annotate it as Sequence[float]. (In the Python type system, float is considered a supertype of int, even though there is no nominal inheritance.)

Another recommendation is to try and be as broad as possible in the parameter types. (This echoes the Python paradigm of dynamic typing.) The idea is that you just specify that the object you expect must be able to "quack", but you don't say it must be a duck.

In your example, since you are reliant on the argument being compatible with sum as well as with len, you should consider what types those functions expect. The len function is simple, since it basically just calls the __len__ method of the object you pass to it. The sum function is more nuanced, but in your case the relevant part is that it expects an iterable of elements that can be added (e.g. float).

If you take a look at the collections ABCs, you'll notice that Sequence actually offers much more than you need, being that it is a reversible collection. A Collection is the broadest built-in type that fulfills your requirements because it has __iter__ (from Iterable) and __len__ (from Sized). So you could do this instead:

from collections.abc import Collection


def average(numbers: Collection[float]) -> float:
    return sum(numbers) / len(numbers)

(By the way, the parameter name should not reflect its type.)

Lastly, if you wanted to go all out and be as broad as possible, you could define your own protocol that is even broader than Collection (by getting rid of the Container inheritance):

from collections.abc import Iterable, Sized
from typing import Protocol, TypeVar


T = TypeVar("T", covariant=True)


class SizedIterable(Sized, Iterable[T], Protocol[T]):
    ...  # Literal ellipsis, not a placeholder


def average(numbers: SizedIterable[float]) -> float:
    return sum(numbers) / len(numbers)

This has the advantage of supporting very broad structural subtyping, but is most likely overkill.

(For the basics of Python typing, PEP 483 and PEP 484 are a must-read.)

Answer from Daniel Fainberg on Stack Overflow
🌐
Python documentation
docs.python.org › 3 › library › typing.html
typing — Support for type hints
from typing import TypeVar, Generic, Sequence class WeirdTrio[T, B: Sequence[bytes], S: (int, str)]: ...
Top answer
1 of 2
38

Good on you for using type annotations! As the documentations says, if you are on Python 3.9+, you should most likely never use typing.Sequence due to its deprecation. Since the introduction of generic alias types in 3.9 the collections.abc classes all support subscripting and should be recognized correctly by static type checkers of all flavors.

So the benefit of using collections.abc.T over typing.T is mainly that the latter is deprecated and should not be used.

As mentioned by jsbueno in his answer, annotations will have no runtime implications either way, unless of course they are explicitly picked up by a piece of code (see my other answer here). They are just an essential part of good coding style. But your function would still work, i.e. your script would execute without error, even if you annotated your function with something absurd like def average(sequence: 4%3): ....


Proper annotations are still extremely valuable. Thus, I would recommend you get used to some of the best practices as soon as possible. (A more-or-less strict static type checker like mypy is very helpful for that.) For one thing, when you are using generic types like Sequence, you should always provide the appropriate type arguments. Those may be type variables, if your function is also generic or they may be concrete types, but you should always include them.

In your case, assuming you expect the contents of your sequence to be something that can be added with the same type and divided by an integer, you might want to e.g. annotate it as Sequence[float]. (In the Python type system, float is considered a supertype of int, even though there is no nominal inheritance.)

Another recommendation is to try and be as broad as possible in the parameter types. (This echoes the Python paradigm of dynamic typing.) The idea is that you just specify that the object you expect must be able to "quack", but you don't say it must be a duck.

In your example, since you are reliant on the argument being compatible with sum as well as with len, you should consider what types those functions expect. The len function is simple, since it basically just calls the __len__ method of the object you pass to it. The sum function is more nuanced, but in your case the relevant part is that it expects an iterable of elements that can be added (e.g. float).

If you take a look at the collections ABCs, you'll notice that Sequence actually offers much more than you need, being that it is a reversible collection. A Collection is the broadest built-in type that fulfills your requirements because it has __iter__ (from Iterable) and __len__ (from Sized). So you could do this instead:

from collections.abc import Collection


def average(numbers: Collection[float]) -> float:
    return sum(numbers) / len(numbers)

(By the way, the parameter name should not reflect its type.)

Lastly, if you wanted to go all out and be as broad as possible, you could define your own protocol that is even broader than Collection (by getting rid of the Container inheritance):

from collections.abc import Iterable, Sized
from typing import Protocol, TypeVar


T = TypeVar("T", covariant=True)


class SizedIterable(Sized, Iterable[T], Protocol[T]):
    ...  # Literal ellipsis, not a placeholder


def average(numbers: SizedIterable[float]) -> float:
    return sum(numbers) / len(numbers)

This has the advantage of supporting very broad structural subtyping, but is most likely overkill.

(For the basics of Python typing, PEP 483 and PEP 484 are a must-read.)

2 of 2
1

Actually, in your code you need neither of those:

Typing with annotations, which is what you are doing with your imported Sequences class is an optional feature, meant for (1) quick documentation; (2) checking of the code before it is run by static code analysers such as Mypy.

The fact is that some IDEs use the result of static checking by default in their recomented configurations, and they can make it look like code without annotations is "faulty": it is not - this is an optional feature.

As long as the object you pass into your function respect some of the Sequence interface it will need, it will work (it needs __len__ and __getitem__ as is)

Just run your code without annotations and see it work:

def average(myvariable):
    return sum(myvariable) / len(myvariable)

That said, here is what is happening: list is "the sequence" by excellence in Python, and implements everything a sequence needs.

typing.Sequence is just an indicator for the static-checker tools that the data marked with it should respect the Sequence protocol, and does nothing at run time. You can't instantiate it. You can inherit from it (probably) but just to specialize other markers for typing, not for anything that will have any effect during actual program execution.

On the other hand collections.abc.Sequence predates the optional typing recomendations in PEP 484: it works as both a "virtual super class" which can indicate everything that works as a sequence in runtime (through the use of isinstance) (*). AND it can be used as a solid base class to implement fully functional cusotm Sequence classes of your own: just inherit from collections.abc.Sequence and implement functional __getitem__ and __len__ methods as indicated in the docs here: https://docs.python.org/3/library/collections.abc.html (that is for read only sequences - for mutable sequences, check collections.abc.MutableSequence, of course).

(*) for your custom sequence implementation to be recognized as a Sequence proper it has to be "registered" in runtime with a call to collections.abc.Sequence.register. However, AFAIK, most tools for static type checking do not recognize this, and will error in their static analysis)

Discussions

Question regarding type-hinting Collection types
So, logic says that here: list[str | int] It means that it's a list that contains ONLY strings OR ONLY integers. Right? Wrong, actually. This is a list of mixed str and int. What you mean would be written list[str] | list[int]. More on reddit.com
🌐 r/Python
10
8
August 26, 2023
Question about python types: What's the difference between a types.Sequence and types.Iterable?
Sequences need to implement __len__ and __getitem__ in addition to being iterable. More on reddit.com
🌐 r/learnpython
12
3
February 25, 2021
Feature Request: Type hint for Fixed Length Homogeneous Sequences
The real thing that we are trying ... is a sequence of floats of length 10. It does not necessarily have to be a Tuple, it just has to be iterable, and have 10 items, that cannot grow larger or smaller. If I wanted to pass the function a List, this should also pass static type checkers, as long as it has 10 floats. ... I am not a core python developer, ... More on github.com
🌐 github.com
7
February 11, 2021
python - What exactly is a Sequence? - Stack Overflow
Return 1 if the object provides sequence protocol, and 0 otherwise. Note that it returns 1 for Python classes with a __getitem__() method unless they are dict subclasses since in general case it is impossible to determine what the type of keys it supports. More on stackoverflow.com
🌐 stackoverflow.com
🌐
Python Like You Mean It
pythonlikeyoumeanit.com › Module2_EssentialsOfPython › SequenceTypes.html
Sequence Types — Python Like You Mean It
Given a sequence, x, and a valid ... to return the index 0. ... We have been introduced to three Python types that are sequential in nature: strings, lists, and tuples....
🌐
Medium
medium.com › nerd-for-tech › sequence-type-and-iterables-in-python-4e2ca6b08981
Sequence Type and Iterables in Python | by Shilpa Sreekumar | Medium
January 13, 2024 - Sequence Type in python are strings, lists, tuples, byte sequences, byte arrays and range objects which are indexable and we can iterate…
🌐
Medium
mrslima.medium.com › python-typing-88923684500b
Python Typing. Annotations & Type Hints for Python 3.5… | by Daniela Lima | Medium
October 6, 2021 - You’ll be needing this there’s no way to specify, if the parameter should be a list or a tuple. However, if you use sequence, you’re saying that both the tuple and the list count as a sequence. And you can also specify what type the sequence should store.
🌐
Snyk
snyk.io › advisor › typing › functions › typing.sequence
How to use the typing.Sequence function in typing | Snyk
from dataclasses import dataclass from typing import Sequence from pcs.common.types import DrRole from pcs.common.interface.dto import DataTransferObject @dataclass(frozen=True) class DrConfigNodeDto(DataTransferObject): name: str @dataclass(frozen=True) class DrConfigSiteDto(DataTransferObject): site_role: DrRole node_list: Sequence[DrConfigNodeDto] @dataclass(frozen=True) class DrConfigDto(DataTransferObject): local_site: DrConfigSiteDto remote_site_list: Sequence[DrConfigSiteDto] @dataclass(frozen=True) class DrSiteStatusDto(DataTransferObject): local_site: bool site_role: DrRole status_plaintext: str status_successfully_obtained: bool
🌐
MeadSteve's Dev Blog
blog.meadsteve.dev › programming › 2023 › 09 › 09 › typed-python-prefer-sequence-over-list
Typed Python: Choose Sequence over List – MeadSteve's Dev Blog
September 9, 2023 - I’ve been working with type hints in python for a few years now. Over time I’ve noticed certain patterns evolving in my code. This will be a short post on one of those patterns. It’s a small pattern where I try and be more precise in what I require or accept as a function input. Or more specifically why I try and default to writing the following: from typing import Sequence def do_a_thing(items: Sequence[float]): ...
Find elsewhere
🌐
Reddit
reddit.com › r/python › question regarding type-hinting collection types
r/Python on Reddit: Question regarding type-hinting Collection types
August 26, 2023 -

I'm trying to make sense of the current type-hinting convention regarding Collection types. For simplicity's sake, I'll use list, tuple and dict, but that applies to any of their variant. Note that none of the following lines of code actually raise an error when run. Here's my question:

When someone writes:

var: str | int

What I read is obviously that 'var' is either a string OR an integer.

Again, when someone writes:

lst: list[str]

What I read is obviously that 'lst' is a list that contains ONLY strings.

So, logic says that here:

list[str | int]

It means that it's a list that contains ONLY strings OR ONLY integers. Right? And in this following case:

list[str, int]

it means that it's a list that contains strings AND/OR integers. Perhaps would it make more sense if it used & instead of , but whatever. Hopefully I'm making sense.

However, when checking the types using mypy, I find out that list[str, int] is not conventional, and that rather you should use list[str | int] for both cases. My code editor also says this, and yours probably does too.

Now take a similarly-structured type, tuple. By similarly-structured, I mean that you access and iterate through tuples the same way, in fact they are both Sequence types. The thing with tuple is that its args are index sensitive. I understand why this is the case and why it makes sense (because of immutability), but what doesn't make sense to me is why mypy is fine now with the comma notation with tuple but not list.

tpl: tuple[str, int] = ("", 0) # fine
tpl: tuple[str, int] = (0, "") # not fine
tpl: tuple[str | int] = (0, "") # not fine
tpl: tuple[str | int] = (0, ) # fine
tpl: tuple[str | int] = ("", ) # fine

Last but not least, dict. Everybody knows that Mapping types are different from Sequence types in the way you access, set or delete their items. So why is the typing convention this:

dict[str, int] # comma notation like tuple

and not:

dict[str: int] # Mapping specific notation 

The latter makes much more sense and is more readable to me, and makes the distinction between Sequence types and Mapping types. Furthermore, the use of commas in Mapping types kinda looks like you can insert as many of them as you want, just like tuple?

tpl: tuple[str, int] = ("", 0) # this is fine
tpl: tuple[str, int, bool] = ("", 0, False) # this is fine
dct: dict[str, int, bool] = {"": 0: False} # but this is not fine

I know that slices are not hashable for a reason, and that dict[str: int] notation kinda implies that they are, but again, the interpreter will raise no errors if you do this, much like if you write list[str, int], so I don't see the problem.

Is there an actual, good reason why this is the way it is?

Top answer
1 of 4
21
So, logic says that here: list[str | int] It means that it's a list that contains ONLY strings OR ONLY integers. Right? Wrong, actually. This is a list of mixed str and int. What you mean would be written list[str] | list[int].
2 of 4
10
You're looking at this purely from the perspective of a "user", and not the perspective of the "author". Let's pretend for a moment that the builtin class list doesn't exist, and you have to implement it. To do that, you need typing.Generic and, more importantly, a typing.TypeVar . The code would look more or less like this: T = typing.TypeVar('T') class List(typing.Generic[T]): def __init__(self): self._elements = [] def append(self, element: T) -> None: self._elements.append(element) As you can see, this class is generic over one TypeVar. So naturally, it also accepts one type argument: >>> List[int] # one argument __main__.List[int] >>> List[int | str] # still one argument __main__.List[int | str] >>> List[int, str] # two arguments TypeError: Too many arguments for ; actual 2, expected 1 (The reason why list[int, str] doesn't throw an error is because the python devs are lazy.) As for tuples, they serve a different purpose than lists. Lists are intended to be a homogeneous data structure, i.e. a sequence of an arbitrary number of items of the same type. Tuples, on the other hand, are intended to have a fixed number of items of (potentially) different types. So naturally tuple must accept an arbitrary number of type arguments. dict[str: int] # Mapping specific notation The latter makes much more sense and is more readable to me, and makes the distinction between Sequence types and Mapping types. This would make python's syntax and parser more complicated - and, frankly, inconsistent - for no real reason. As far as the type system is concerned, there is no difference between a sequence and a mapping. They're both just Generics, albeit with a different number of TypeVars.
🌐
Real Python
realpython.com › python-sequences
Python Sequences: A Comprehensive Guide – Real Python
March 18, 2026 - This tutorial dives into Python sequences, which is one of the main categories of data types. You'll learn about the properties that make an object a sequence and how to create user-defined sequences.
🌐
Basicexamples
basicexamples.com › example › python › typing-sequence
Basic example of typing.Sequence in Python
from typing import Sequence def print_sequence(seq: Sequence) -> None: for item in seq: print(item) numbers = [1, 2, 3, 4, 5] print_sequence(numbers)
🌐
GitHub
github.com › python › typing › issues › 786
Feature Request: Type hint for Fixed Length Homogeneous Sequences · Issue #786 · python/typing
February 11, 2021 - Currently, the recommended way to add type hints to fixed-length sequences is to use Tuples 1.
Author: python
Top answer
1 of 3
18

Brief introduction to typing in Python

Skip ahead if you know what structural typing, nominal typing and duck typing are.

I think much of the confusion arises from the fact that typing was a provisional module between versions 3.5 and 3.6. And was still subject to change between versions 3.7 and 3.8. This means there has been a lot of flux in how Python has sought to deal with typing through type annotations.

It also doesn't help that python is both duck-typed and nominally typed. That is, when accessing an attribute of an object, Python is duck-typed. The object will only be checked to see if it has an attribute at runtime, and only when immediately requested. However, Python also has nominal typing features. Nominal typing is where one type is declared to be a subclass of another. This can be through inheritance, or with the register() method of ABCMeta.

typing originally introduced its types using the idea of nominal typing. As of 3.8 it is trying to allow for the more pythonic structural typing. Structural typing is related to duck-typing, except that it is taken into consideration at "compile time" rather than runtime. For instance, when a linter is trying to detect possible type errors -- such as if you were to pass a dict to a function that only accepts sequences like tuples or list. With structural typing, a class B should be considered a subtype of A if it implements the all the methods of A, regardless of whether it has been declared to be a subtype of A (as in nominal typing).

Answer

sequences (little s) are a duck type. A sequence is any ordered collection of objects that provides random access to its members. Specifically, if it defines __len__ and __getitem__ and uses integer indices between 0 and n-1 then it is a sequence. A Sequence (big s) is a nominal type. That is, to be a Sequence, a class must be declared as such, either by inheriting from Sequence or being registered as a subclass.

A numpy array is a sequence, but it is not a Sequence as it is not registered as a subclass of Sequence. Nor should it be, as it does not implement the full interface promised by Sequence (things like count() and index() are missing).

It sounds like you want is a structured type for a sequence (small s). As of 3.8 this is possible by using protocols. Protocols define a set of methods which a class must implement to be considered a subclass of the protocol (a la structural typing).

from typing import Protocol
import numpy as np

class MySequence(Protocol):
    def __getitem__(self, index):
        raise NotImplementedError
    def __len__(self):
        raise NotImplementedError
    def __contains__(self, item):
        raise NotImplementedError
    def __iter__(self):
        raise NotImplementedError

def f(s: MySequence):
    for i in range(len(s)):
        print(s[i], end=' ')
    print('end')

f([1, 2, 3, 4]) # should be fine
arr: np.ndarray = np.arange(5)
f(arr) # also fine
f({}) # might be considered fine! Depends on your type checker

Protocols are fairly new, so not all IDEs/type checkers might support them yet. The IDE I use, PyCharm, does. It doesn't like f({}), but it is happy to consider a numpy array a Sequence (big S) though (perhaps not ideal). You can enable runtime checking of protocols by using the runtime_checkable decorator of typing. Be warned, all this does is individually check that each of the Protocols methods can be found on the given object/class. As a result, it can become quite expensive if your protocol has a lot of methods.

2 of 3
1

I think the most practical way to define a sequence in Python is 'A container that supports indexing with integers'.

The Wikipedia definition also holds:

a sequence is an enumerated collection of objects in which repetitions are allowed and order does matter.

To validate if an object is a sequence, I would emulate the logic from the Sequence Protocol:

hasattr(test_obj, "__getitem__") and not isinstance(test_obj, collections.abc.Mapping) 
🌐
Python Tutorial
pythontutorial.net › home › advanced python › python sequences
Python Sequences
March 27, 2025 - The mutable sequence types are lists and bytearrays while the immutable sequence types are strings, tuples, range, and bytes.
Top answer
1 of 7
19

Not supported by typeshed

Apparently, this is not possible with type hints. PEP 484 can not distinguish between Sequence[str], Iterable[str] and str according to Guido van Rossum.

Source: https://github.com/python/mypy/issues/1965 and https://github.com/python/typing/issues/256

So far, none of the proposals to fix this in typeshed have made it.

Support by individual type checkers

There have been some discussions if individual type checkers should support the distinction, but so far, this has not happened because most type checkers prefer to only follow the typing specification (and thus ensure that the behaviour is common to all Python type checkers).

Since 2021, pytype is the only type checker to treat str as a special case that does not match against Sequence[str] or Iterable[str]. Source: pytype FAQ on str.

A request to implement this in pyright has been rejected, with the possibility to reopen once enough upvotes are cast (a thumbs up to the first post in https://github.com/microsoft/pyright/issues/4886).

2 of 7
6

I couldn't find anything about the type exclusion or the type negation, seems like it's not supported in current version of Python 3. So the only distinctive feature of strings that crossed my mind is that strings are immutable. Maybe it'll help:

from typing import Union
from collections.abc import MutableSequence


MySequenceType = Union[MutableSequence, tuple, set]
def foo(a: MySequenceType):
    pass

foo(["09485", "kfjg", "kfjg"]) # passed
foo(("09485", "kfjg", "kfjg")) # passed
foo({"09485", "kfjg", "kfjg"}) # passed
foo("qwerty") # not passed
🌐
Python
docs.python.org › 3 › builtins › stdtypes.html
Built-in Types — Python 3.14.7 documentation
The following sections describe the standard types that are built into the interpreter. The principal built-in types are numerics, sequences, mappings, classes, instances and exceptions. Some colle...
🌐
YouTube
youtube.com › watch
Explain sequence data type in python | understand list, tuple, string and range range in python - YouTube
Sequence data type in python has following subtypes like list, tuple, string and range. This makes sequence data type in python one of the most powerful data...
Published: January 26, 2026
Top answer
1 of 3
350

Until Python 3.9 added support for type hinting using standard collections, you had to use typing.Tuple and typing.List if you wanted to document what type the contents of the containers needed to be:

def f(points: Tuple[float, float]):
    return map(do_stuff, points)

Up until Python 3.8, tuple and list did not support being used as generic types. The above example documents that the function f requires the points argument to be a tuple with two float values.

typing.Tuple is special here in that it lets you specify a specific number of elements expected and the type of each position. Use ellipsis if the length is not set and the type should be repeated: Tuple[float, ...] describes a variable-length tuple with floats.

For typing.List and other sequence types you generally only specify the type for all elements; List[str] is a list of strings, of any size. Note that functions should preferentially take typing.Sequence as arguments and typing.List is typically only used for return types; generally speaking most functions would take any sequence and only iterate, but when you return a list, you really are returning a specific, mutable sequence type.

If you still need to support Python 3.8 or older code, you should always pick the typing generics even when you are not currently restricting the contents. It is easier to add that constraint later with a generic type as the resulting change will be smaller.

If you are implementing a custom container type and want that type to support generics, you can implement a __class_getitem__ hook or inherit from typing.Generic (which in turn implements __class_getitem__).

2 of 3
189

From Python 3.9 (PEP 585) onwards tuple, list and various other classes are now generic types. Using these rather than their typing counterpart is now preferred. From Python 3.9 you can now just do:

def f(points: tuple[float, float]):
    return map(do_stuff, points)

If you don't need to evaluate your type hints then you can use this syntax in Python 3.7+ due to PEP 563.

from __future__ import annotations


def f(points: tuple[float, float]):
    return map(do_stuff, points)

You should always pick then non-typing generic whenever possible as the old typing.Tuple, typing.List and other generics are deprecated and will be removed in a later version of Python.

Importing those from typing is deprecated. Due to PEP 563 and the intention to minimize the runtime impact of typing, this deprecation will not generate DeprecationWarnings. Instead, type checkers may warn about such deprecated usage when the target version of the checked program is signalled to be Python 3.9 or newer. It's recommended to allow for those warnings to be silenced on a project-wide basis.

The deprecated functionality will be removed from the typing module in the first Python version released 5 years after the release of Python 3.9.0.

🌐
GitHub
github.com › python › typing › discussions › 1145
Nested sequence type fails to detect errors · python/typing · Discussion #1145
This solution is used by numpy._typing._nested_sequence._NestedSequence. When a function argument is annotated as _NestedSequence[int], mypy does not raise errors when literal values are used: def func1(a: _NestedSequence[int]) -> int: ... # Fails as expected: # Incompatible types in assignment (expression has type "int", variable has type "_NestedSequence[int]") [assignment]mypy(error) v1 = func1(1) # type: ignore[arg-type] # Does not fail as expected v2 = func1([1]) # Does not fail as expected v3 = func1([[1]]) # Does not fail, but I expected a failure v4 = func1(["a"]) # [arg-type] error expected v5 = func1([["a"]]) # [arg-type] error expected # Does fail as expected input_: List[str] = ["a"] input__: List[List[str]] = [["a"]] v4_ = func1(input_) # type: ignore[arg-type] v5 = func1(input__) # type: ignore[arg-type]
Author: python
🌐
Art of Problem Solving
artofproblemsolving.com › wiki › index.php › Sequence_(Python)
Sequence (Python) - AoPS Wiki
mySeq[i] will return the i'th character of mySeq. Sequences in Python are zero-indexed, so the first element has index 0, the second has index 1, and so on.