Search code examples
pythontypestype-hinting

Python type hint for Iterable[str] that isn't str


In Python, is there a way to distinguish between strings and other iterables of strings?

A str is valid as an Iterable[str] type, but that may not be the correct input for a function. For example, in this trivial example that is intended to operate on sequences of filenames:

from typing import Iterable

def operate_on_files(file_paths: Iterable[str]) -> None:
    for path in file_paths:
        ...

Passing in a single filename would produce the wrong result but would not be caught by type checking. I know that I can check for string or byte types at runtime, but I want to know if it's possible to catch silly mistakes like that with a type-checking tool.

I've looked over the collections.abc module and there doesn't seem to be any abc that would include typical iterables (e.g. lists, tuples) but exclude strings. Similarly, for the typing module, there doesn't seem to be a type for iterables that don't include strings.


Solution

  • As of March 2022, the answer is no.

    This issue has been discussed since at least July 2016. On a proposal to distinguish between str and Iterable[str], Guido van Rossum writes:

    Since str is a valid iterable of str this is tricky. Various proposals have been made but they don't fit easily in the type system.

    You'll need to list out all of the types that you want your functions to accept explicitly, using Union (pre-3.10) or | (3.10 and higher).

    e.g. For pre-3.10, use:

    from typing import Union
    ## Heading ##
    def operate_on_files(file_paths: Union[TypeOneName, TypeTwoName, etc.]) -> None:
        for path in file_paths:
            ...
    

    For 3.10 and higher, use:

    ## Heading ##
    def operate_on_files(file_paths: TypeOneName | TypeTwoName | etc.) -> None:
        for path in file_paths:
            ...
    

    If you happen to be using Pytype, it will not treat str as an Iterable[str] (as pointed out by Kelly Bundy). But, this behavior is typechecker-specific, and isn't widely supported in other typecheckers.