Skip to content

Allow mypy to flag str matching against Sequence[str] as an error #11001

@sirosen

Description

@sirosen

Feature

A new optional setting for mypy which forbids matching str against Sequence[str] or Iterable[str]. This will catch a wide variety of bugs which users encounter when defining APIs which consume these types and are unintentionally passed strings. It also allows correct overloading of str vs Sequence[str] to describe behavior.

The option would require the explicit (but "redundant") Union[str, Sequence[str]] or Union[str, Iterable[str]] for values which can be a string or non-string iterable of strings.

Pitch

python/typing#256 has been an open issue for a while, with no clear resolution within the type system itself.

However the pytype maintainers recently weighed in to mention that they took this approach, forbidding str from matching Sequence[str], and their users have been happy with the results. I think mypy should match this behavior if possible/feasible.

In answer to Guido's question about whether or not this should apply to AnyStr, the answer is "ideally yes". However, if that's significantly harder, only applying to str is probably fine: str is likely the most common case for python developers.

Use Cases

# simple function which the author doesn't realize can consume a single string
# count_starts_lowercase("some string") should fail
def count_starts_lowercase(x: Sequence[str]) -> int:
    return len([c for c in x if c[0] in string.ascii_lowercase])

# function with overloaded definitions based on str/Sequence[str]
# the following definition should work
@typing.overload
def poly_getlen(value: str) -> int: ...
@typing.overload
def poly_getlen(value: Sequence[str]) -> List[int]: ...
def poly_getlen(value: Union[str, Sequence[str]]) -> Union[int, List[int]]:
    if isinstance(value, str):
        return len(value)
    return [len(x) for x in value]

# a sequence of unions containing str should be carefully considered
# ideally `count_terminal_digit_is_0("00011101")` would fail
# if this adds too much complexity, it can be allowed
def count_terminal_digit_is_0(x: Sequence[Union[str, int]]) -> int:
    terminal_digits = [(n % 10 if isinstance(n, int) else n[-1]) for n in x]
    return len([d for d in terminal_digits if d in (0, "0")])

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions