You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Problem with str being treated as Sequence[str] #5090
$ mypy file.py
file.py:2: error: Revealed type is 'builtins.dict[builtins.str*, typing.Sequence*[builtins.str]]'
file.py:3: error: Incompatible types in assignment (expression has type"Sequence[str]", variable has type"str")
So, as far as I understand, mypy tried to find the strictest type for values of d, which is Sequence[str] in this case. In Python, string is a Sequence of (sub)strings (s[0] == s[0][0] == s[0][0][0]), also list is a Sequence, so this is understandable. Unfortunately, the rule that string is a sequence of (sub)strings does not work in case of value: str = d['k1'].
Expected output
To get rid of the error I need to manually give more general type: d: Dict[str, Any], so this is not such a big problem. Having said that, expected behavior would be: No "Incompatible types in assignment" error for the given code.
This is interesting. We can suggest workarounds etc. for the example code,
but I wonder -- does any code that declares it wants a Sequence[str] really
expect a single str object?
We could remove the Sequence[str] and instead add explicit dunder methods
to str objects. It would still be seen as Sized,
Iterable[str], Container[str] (in 3.6+), and Reversible[str], because those
are all Protocols. But Sequence is not, and(for the first time ever!) we
would have a way to state that a function takes a Sequence[str] but not a
str.
Not sure how much code would break...
Reacted by Paweł Święcki, Gabriel, Dominic , Sascha Desch, Sayandip Dutta, Erlend vollset, Neil Girdhar and Mathijs Mortimer
$ mypy file2.py
file2.py:2: error: Revealed type is 'builtins.dict[builtins.str*, typing.Sequence*[builtins.str]]'
file2.py:5: error: Revealed type is 'builtins.dict[builtins.str*, builtins.object*]'
I'd say type of dict's value in the second shoud be, analogically, Sequence[int], no?
EDIT: I think I can guess: in the first case empty list is treated kinda as an empty string, which is quite wrong.
We can suggest workarounds etc. for the example code, but I wonder -- does any code that declares it wants a Sequence[str] really expect a single str object?
Yep!
For example, in Hypothesis we have sampled_from, which supports ordered collections including Sequence, OrderedDict, and Enum. Excluding str from that would just force a pointless conversion from sampled_from(astring) to sampled_from(list(astring)) on all our users, or we would have to add string types to the type hint.
So at the very least, I have a strong concrete (as well as aesthetic) preference to keep treating str as Sequence[str] whenever needed.
We can suggest workarounds etc. for the example code, but I wonder -- does any code that declares it wants a Sequence[str] really expect a single str object?
I also have a use case for this, where I have parsers some of which expect Sequence[T] (or Iterable[T]) and others of which expect T, and I'd like to be able to use them together when T == str in combinators that (e.g.) sequence one parser after another.
I think both the runtime behavior of treating a string as instance of collections.abc.Iterable and static checks like hello world matching Iterable[str] are design defects introducing weird corner cases in the code of most users. I suspect the following is a canonical example of unexpected behavior:
Given that the runtime behavior of str is unlikely to change (I assume?), at least making mypy respect the difference between Iterable[str] and str will warn the users of this pitfall.
Yes, and this is actually really nice, having to deal with character types adds a lot of unneeded complexity. The correct way to deal with this is that whenever you want an Iterable[str], have an assert not isinstance(seq, str) or similar, to make sure it isn't passed a str. Sometimes handing a function a str is acceptable though...
A clean but albeit somewhat ugly solution would be to introduce new types in typing (and hopefully also new abstract base classes in collections.abc) for iterables and sequences that treat str as atomic and not a member of said types.
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
What should be the names of the new Sequence and Iterable? My previous comment was made with the thought in mind that in properly typed code the use of NewSequence and NewIterable would probably be higher than of the regular Sequence and Iterable.
In the spirit of the python Zen (explicit is better than implicit, there should be one obvious way to do it, yada yada) annotations like Union[str, NewIterable[str]] for cases where both a single string and an iterable sequence of strings are valid, beat the current situation imho.
Reacted by Serbitar, Freek Dijkstra, Atreju Tauschinsky, oub-vtl, Radu Grosu and Mathijs Mortimer
One way to deal with this would be to introduce a new optional error that is generated when str is used while Iterable[str] or Sequence[str] is expected. It might be hard to catch all cases of this, but at least the most common ones such as passing an str argument, assigning a str value and iterating over a str should not be difficult to handle.
One way to deal with this would be to introduce a new optional error that is generated when str is used while Iterable[str] or Sequence[str] is expected. It might be hard to catch all cases of this, but at least the most common ones such as passing an str argument, assigning a str value and iterating over a str should not be difficult to handle.
@JukkaL how challenging do you think it would be implement a putative --warn-iterable-anystr or --no-iterable-anystr in mypy? I'd imagine that the semantics would be as you described...
Raise a warning or error when an AnyStr (str, bytes) is used where an abstract collection of the type (eg. Iterable[str], Iterable[AnyStr]) is expected.
Unless the AnyStr type is provided via a Union (eg. Union[str, Iterable[str]]).
This applies to the collections.abc abstract types (and matching typing aliases) : Iterable, Collection, Sequence.
For example:
defjust_sequence(vals: Iterable[str]) ->Any:
...
defsequence_or_str(val_or_vals: Union[str, Iterable[str]]) ->Any:
...
# mypy error or warning if `--warn-iterable-anystr` or `--no-iterable-anystr` is providedjust_sequence("bad")
just_sequence(["this", "is", "fine"])
sequence_or_str("this_is_fine")
sequence_or_str(["this", "is", "fine"])
However it seems clear from the discussion here and elsewhere that a typing-level or language level change would be controversial. I think support for this error detection in mypy would be a valuable immediate addition and may provide a useful starting point to determine if this issue warrants enough effort to consider typing level enhancements to address it in the future.
I don't believe this can be implemented through mypy's existing plugin interface, but would be very happy to understand if you think the plugin interface can support this kind of feature.
Reacted by Oscar Täckström, Fabian Keller and Daniel Himmelstein
Activity
[-]Problem with treating `str` as `Sequence[str]`[/-][+]Problem with `str` being treated as `Sequence[str]`[/+]gvanrossum commentedon May 21, 2018
pawelswiecki commentedon May 21, 2018
@gvanrossum Thank you for the reply.
One more thing (which is probably obvious for someone more knowlegable). Why mypy treats those two cases differently:
mypy:
I'd say type of dict's value in the second shoud be, analogically,
Sequence[int], no?EDIT: I think I can guess: in the first case empty list is treated kinda as an empty string, which is quite wrong.
ilevkivskyi commentedon May 21, 2018
The second example is a known issue #5045
Zac-HD commentedon May 22, 2018
Yep!
For example, in Hypothesis we have
sampled_from, which supports ordered collections includingSequence,OrderedDict, andEnum. Excludingstrfrom that would just force a pointless conversion fromsampled_from(astring)tosampled_from(list(astring))on all our users, or we would have to add string types to the type hint.So at the very least, I have a strong concrete (as well as aesthetic) preference to keep treating
strasSequence[str]whenever needed.gvanrossum commentedon May 22, 2018
bwo commentedon May 24, 2019
I also have a use case for this, where I have parsers some of which expect
Sequence[T](orIterable[T]) and others of which expectT, and I'd like to be able to use them together whenT == strin combinators that (e.g.) sequence one parser after another.stereobutter commentedon Sep 24, 2019
I think both the runtime behavior of treating a string as instance of
collections.abc.Iterableand static checks likehello worldmatchingIterable[str]are design defects introducing weird corner cases in the code of most users. I suspect the following is a canonical example of unexpected behavior:where the intended use case is
and an unintended side effects of the implementation is
Given that the runtime behavior of
stris unlikely to change (I assume?), at least making mypy respect the difference betweenIterable[str]andstrwill warn the users of this pitfall.bwo commentedon Sep 24, 2019
But
stris an iterable ofstr. Making mypy lie about what is actually the case because it might be an error seems extremely backwards to me.emmatyping commentedon Sep 24, 2019
Yes, and this is actually really nice, having to deal with character types adds a lot of unneeded complexity. The correct way to deal with this is that whenever you want an
Iterable[str], have anassert not isinstance(seq, str)or similar, to make sure it isn't passed astr. Sometimes handing a function astris acceptable though...stereobutter commentedon Sep 26, 2019
A clean but albeit somewhat ugly solution would be to introduce new types in
typing(and hopefully also new abstract base classes incollections.abc) for iterables and sequences that treatstras atomic and not a member of said types.Example:
I think this is do-able but alas
What should be the names of the new
SequenceandIterable? My previous comment was made with the thought in mind that in properly typed code the use ofNewSequenceandNewIterablewould probably be higher than of the regularSequenceandIterable.In the spirit of the python Zen (explicit is better than implicit, there should be one obvious way to do it, yada yada) annotations like
Union[str, NewIterable[str]]for cases where both a single string and an iterable sequence of strings are valid, beat the current situation imho.wei-hai commentedon Jul 27, 2020
experiencing the same issue, any update?
JukkaL commentedon Jul 31, 2020
One way to deal with this would be to introduce a new optional error that is generated when
stris used whileIterable[str]orSequence[str]is expected. It might be hard to catch all cases of this, but at least the most common ones such as passing anstrargument, assigning astrvalue and iterating over astrshould not be difficult to handle.bwo commentedon Jul 31, 2020
Again,
stris anIterable[str].My understanding of the goal of mypy as a project is that it's a type system for Python as it exists, not as it might be if it were slightly nicer.
asford commentedon Aug 6, 2021
@JukkaL how challenging do you think it would be implement a putative
--warn-iterable-anystror--no-iterable-anystrinmypy? I'd imagine that the semantics would be as you described...AnyStr(str,bytes) is used where an abstract collection of the type (eg.Iterable[str],Iterable[AnyStr]) is expected.AnyStrtype is provided via aUnion(eg.Union[str, Iterable[str]]).collections.abcabstract types (and matchingtypingaliases) :Iterable,Collection,Sequence.For example:
This feels like a longstanding issue, eg python/typing#256, and there's strong evidence that this is both a common error and pain point in the language. So much so, that it's one of the very few deviations between python and starlark's semantics.
However it seems clear from the discussion here and elsewhere that a
typing-level or language level change would be controversial. I think support for this error detection inmypywould be a valuable immediate addition and may provide a useful starting point to determine if this issue warrants enough effort to considertypinglevel enhancements to address it in the future.I don't believe this can be implemented through
mypy's existing plugin interface, but would be very happy to understand if you think the plugin interface can support this kind of feature.hauntsaninja commentedon Sep 23, 2023
Closing as a duplicate of the more popular #11001. I've also added a
SequenceNotStrtype to https://github.com/hauntsaninja/useful_types