-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
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")])