I still think this is approaching the problem from the wrong direction – IIUC, the problem is not that strings are Iterable, but that they are specifically Iterable[str]
. And most of the time a function is expecting a Iterable[str]
it will work with a str
, but not with the result that’s expected – technically it’s a value error, not a type error, but practically, it’s a type error (and I think the most insidious common one in Python since integer division was removed).
So what would solve the problem is a way to define any iterable[str] except not str
itself – i.e,
Iterable[str] - str
Yes, that would be a bit klunky, considering that’s it’s probably the most common use case [*] – but if there was a way to define it, then it could be given an alias, and no more klunk.
What I wonder is what other applications there are for “subtracting” types – if this is the only one, then probably better to put a kludge [uh – special case] in the type checkers.
[*] maybe not unheard of to take both an Iterable[str] and an [str] – this has been an ongoing issue long before static typing – and there’s plenty of code out there (at least in my codebases
) like:
def fun(bunch_o_strings):
if isinstance(bunch_o_strings, str):
bunch_o_strings = [bunch_o_strings]
for string in bunch_o_strings:
do_something(string)
And that can be correctly typed as Iterable[str]
right now.
And that doesn’t just “fix” the type error, it provides a “nicer” API – maybe the same people that want strict typing wouldn’t like such a squishy API, but my scripting users do 
I think the answer here may be that it’s impossible to describe a type that doesn’t exist, but pretend that an existing type is that type. It wouldn’t be the least bit hard if there actually WAS an AtomicString
But anyway: these issues with str
being an iterable of str
have existed for years, long before static typing – so maybe it’s too much to expect the type system to solve it without making any changes – if folks really need an AtomicString, then maybe make one?
But i don’t think the problem actual is that str
is iterable – that’s actually handy sometimes. The problem is that str
yields str
when you iterate – and that goes on forever.
When you have nested lists, you can iterate and get a list, and iterate and get a list, but eventually, you get to some non-list type.
When you iterate a numpy array you get a lower rank numpy array, but eventually you get a scalar.
When you iterate a str
what you always get is a length-one str
– forever. So maybe rather than an AtomicString, we need a Char.
Maybe the type system could consider a string an Iterator[Char]
– so it would not satisfy Iterator[‘str’] anymore. And I think that could be done without there actually being a Char
.
Hmm – then you would need to cast when you DO iterate through a string and want them to be considered strings. OR could you define a type in the type system that doesn’t actually exist?
Maybe back to Iterable[str]
- str – if there could be a way to spell that.