[Python-ideas] strings as iterables - from str.startswith taking any iterator instead of just tuple
spir
denis.spir at gmail.com
Fri Jan 3 15:21:31 CET 2014
On 01/03/2014 12:41 PM, Nick Coghlan wrote:
> The underlying problem is that strings have a dual nature: you can
> view them as either a sequence of code points (which is how Python
> models them), or else you can view them as an opaque chunk of text
> (which is often how you want to treat them in code that accepts either
> containers or atomic values and treats them differently).
>
> This has some interesting implications for API design.
>
> "def f(*args)" handles the constraint fairly well, as f("astring") is
> treated as a single value and f(*"string") is an unlikely mistake for
> anyone to make.
>
> "def f(iterable)" has problems in many cases, since f("string") is
> treated as an iterable of code points, even if you'd prefer an
> immediate error.
>
> "def f(iterable_or_atomic)" also has problems, since strings will use
> the "iterable" path, even if the atomic handling would be more
> appropriate.
>
> Algorithms that recursively descend into containers also need to deal
> with the fact that doing so with strings causes an infinite loop
> (since iterating over a string produces length 1 strings).
>
> This is a genuine problem, which is why the question of how to cleanly
> deal with these situations keeps coming up every couple of years, and
> the current state of the art answer is "grit your teeth and use
> isinstance(obj, str)" (or a configurable alternative).
>
> However, I'm wondering if it might be reasonable to add a new entry in
> collections.abc for 3.5:
>
>>>> >>>from abc import ABC
>>>> >>>from collections.abc import Iterable
>>>> >>>class Atomic(ABC):
> ... @classmethod
> ... def __subclasshook__(cls, subclass):
> ... if not issubclass(subclass, Iterable):
> ... return True
> ... return NotImplemented
> ...
>>>> >>>Atomic.register(str)
> <class 'str'>
>>>> >>>Atomic.register(bytes)
> <class 'bytes'>
>>>> >>>Atomic.register(bytearray)
> <class 'bytearray'>
>>>> >>>isinstance(1, Atomic)
> True
>>>> >>>isinstance(1.0, Atomic)
> True
>>>> >>>isinstance(1j, Atomic)
> True
>>>> >>>isinstance("Hello", Atomic)
> True
>>>> >>>isinstance(b"Hello", Atomic)
> True
>>>> >>>isinstance((), Atomic)
> False
>>>> >>>isinstance([], Atomic)
> False
>>>> >>>isinstance({}, Atomic)
> False
>
> Any type which wasn't iterable would automatically be considered
> atomic, while some types which *are* iterable could *also* be
> registered as atomic (with str, bytes and bytearray being the obvious
> candidates, as shown above).
>
> Armed with such an ABC, you could then write an "iter_non_atomic"
> helper function as:
>
> def iter_non_atomic(iterable):
> if isinstance(iterable, Atomic):
> raise TypeError("{!r} is considered
> atomic".format(iterable.__class__.__name__)
> return iter(iterable)
I like this solution. But would live with checking for type (usually str). The
point is that, while not that uncommon, when the issue arises one has to deal
with it at one or at most a few places in code (typically at start of one a few
methods of a given type). It is not as if we had to carry an unneeded overload
about everywhere.
Denis
More information about the Python-ideas
mailing list