[Python-ideas] Introduce collections.Reiterable

Nick Coghlan ncoghlan at gmail.com
Mon Sep 23 10:44:12 CEST 2013


On 23 September 2013 18:04, Stephen J. Turnbull <stephen at xemacs.org> wrote:
> Executive summary:
>
> The ability to create a quick iterable with just a simple __getitem__
> is cool and not a "hack" (ie, no need whatsoever to deprecate it), but
> it is clearly a "consenting adults" construction (which includes
> "knowing where your children are at 10pm").
>
> Steven D'Aprano writes:
>
>  > I agree, and I disagree with Nick's characterization of the
>  > sequence protocol as a "backwards-compatibility hack". It is an
>  > elegant protocol
>
> Gotta disagree with you there (except I agree there's no need for a
> word like "hack").  Because __getitem__ is polymorphic (at the
> abstract level of duck-typing), this protocol is ugly.  The "must
> accept 0" clause is a wart.

I think others object to the word "hack" more than I do (or give it
additional implications like "in danger of being deprecated"). To me
it's just a shorthand for saying "this is a case where practicality
beat purity". Just because something is a hack doesn't mean it isn't
useful and isn't a good idea.

I consider functools.wraps to be a hack that managed to preserve
introspectability of most decorated functions with minimal development
effort. runpy and the -m switch took quite a while to evolve into
something that wasn't a hack (although they still have some hacky
parts due to limitations of the import protocol). The code that makes
objects that override __eq__ without overriding __hash__ non-hashable
(and the associated "__hash__ = None") trick is a hack. Python 3's new
super is incredibly nice and easy to use, but there's also a lot of
hackery lurking behind it.

The fact Python 3 lets you create ranges you can't directly take the
length of is a bit of a hack, too (because of the pain involved in
defining an alternative __len__ protocol that didn't funnel everything
through an ssize_t value):

>>> x = range(10**100)
>>> len(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C ssize_t
>>> (x.stop - x.start) // x.step
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

To create a properly defined iterable in modern Python, you must
either implement __iter__ or implement an iter() compatible
__getitem__ and explicitly register with Iterable (to indicate that
your __getitem__ *is* compatible with the fallback protocol in
iter()). Steven's right that I left out that second alternative when
stating what it takes for an item to be considered an iterable, but I
still consider the __getitem__ fallback to be just a neat backwards
compatibility hack for sequences that were defined before the iterator
protocol existed and before the Iterable ABC provided a way to
explicitly declare that your __getitem__ implementation was compatible
with the sequence-iterator protocol.

I was also wrong about iter() checking for __len__ - that's part of
the sequence API fallback in reversed(), rather than the one in
iter():

>>> class InfiniteIter:
...     def __getitem__(self, idx):
...         return idx
...
>>> reversed(InfiniteIter())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'InfiniteIter' has no len()

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list