[Python-3000] Iterators for dict keys, values, and items == annoying :)

Guido van Rossum guido at python.org
Thu Mar 23 23:58:02 CET 2006


On 3/23/06, Ian Bicking <ianb at colorstudy.com> wrote:
> Guido van Rossum wrote:
> > On 3/23/06, Ian Bicking <ianb at colorstudy.com> wrote:
> >
> >>This has been my personal experience with the iterators in SQLObject as
> >>well.  The fact that an empty iterator is true tends to cause particular
> >>problems in that case, though I notice iterkeys() acts properly in this
> >>case; maybe part of the issue is that I'm actually using iterables
> >>instead of iterators, where I can't actually test the truthfulness.
> >
> > This sounds like some kind of fundamental confusion -- you should
> > never be tempted to test an iterator for its truth value.
>
> I'm testing if it is empty or not, which seems natural enough.  Or would
> be, if it worked.

Testing whether an iterator is empty or not is an oxymoron; the only
legit way is to call next() and see whether it raises StopIteration.
This is the fundamental confusion I am talking about. It is NOT
"natural enough". It reveals a fundamental misunderstanding of the
design of the iterator protocol.

(There's also a design bug in 2.4 which perpetuates the confusion,
unfortunately; see below.)

> So I start out doing:
>
>    for item in select_results: ...
>
> Then I realize that the zero-item case is special (which is common), and do:
>
>    select_results = list(select_results)
>    if select_results:
>        ...
>    else:
>        for item in select_results:...

You should write that like this:

  empty = True
  for item in select_results:
    empty = False
    ...
  if empty:
    ...

> That's not a very comfortable code transformation.  When I was just
> first learning Python I thought this would work:
>
>    for item in select_results:
>        ...
>    else:
>        ... stuff when there are no items ...
>
> But it doesn't work like that.

Another fundamental confusion (about the for loop's else clause). It
can't mean two different things. It means "if I didn't break out of
the loop with a break statement".

> .iterkeys() does return an iterator with a useful __len__ method, so the
> principle that iterators shouldn't be tested for truth doesn't seem right.

Which iterkeys()? This is dependent on the object and on the Python
version; Python 2.4 accidentally implemented __len__ on certain
built-in iterators, which may explain why you are seeing this. It
doesn't work pre-2.4 not post-2.5, at least not for dict.iterkeys().

> (Very small mostly unrelated problem that occurs to me just at this
> moment -- I can't override __len__ with any implementation that isn't
> really cheap, because lots of code calls __len__ under the covers, like
> list() -- originally SQLObject used len(query) to do a COUNT(*) query,
> but that didn't work)

Except in 2.4, you can avoid most implicit __len__ calls by
implementing __nonzero__ separately; bool(x) tries __nonzero__ before
__len__. Unfortunately, the iterator accelerator in 2.4 is called
__len__ so various code tries to call __len__ when converting an
iterator to a list/tuple. 2.3 didn't; 2.5 won't.

--
--Guido van Rossum (home page: http://www.python.org/~guido/)


More information about the Python-3000 mailing list