Boolean tests [was Re: Attack a sacred Python Cow]

Matthew Woodcraft mattheww at chiark.greenend.org.uk
Thu Jul 31 17:01:48 EDT 2008


Steven D'Aprano wrote:
>On Wed, 30 Jul 2008 20:55:03 +0100, Matthew Woodcraft wrote:

>> On the other hand, iterators provide a clear example of problems with
>> "if x": __nonzero__ for iterators (in general) returns True even if they
>> are 'empty'.

> How do you propose telling whether an iterator is empty?
> That's a generic problem with any sort of lazy function. You don't know
> if it has finished unless you try grabbing data from it.

Of course.

The point is that if you tell people that "if x" is the standard way to
check for emptiness, and also support a general principle along the
lines of "write your function using the interfaces you expect, and call
it using the object you have", you should expect to end up with bugs of
this sort.


>> For example, this function (which attempts to avoid making an expensive
>> call when not necessary) is buggy, but easy to write if you've been
>> taught that "if x" will work with any kind of object.
>>
>> def frob(widgets, power):
>>     if widgets:
>>         frobber = Frobber(power) # expensive call
>>         for widget in widgets:
>>             frobber.frob(widget)

> AFAIK there's no great solution to this problem. It's inherent in the way
> lazy functions work. Certainly you can't replace the call to "if widgets"
> with "if len(widgets)", because iterators don't have a length.

I'm not a fan of len() for testing emptiness. But it would have been
better in this case, because it would have converted a hard-to-find
performance bug into an obvious exception.


>However, there is a good (but not great) solution:
>
>def frob(widgets, power):
>    widgets = iter(widgets)  # in case widgets is a sequence
>    try:
>        first = widgets.next()
>    except StopIteration:
>        # empty iterator, nothing to do
>        return None
>    frobber = Frobber(power) # expensive call
>    frobber.frob(widget)
>    for widget in widgets:
>        frobber.frob(widget)

I would use the following low-tech way:

def frob(widgets, power):
    frobber = None
    for widget in widgets:
        if frobber is None:
            frobber = Frobber(power)
        frobber.frob(widget)

-M-



More information about the Python-list mailing list