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

giltay at gmail.com giltay at gmail.com
Tue Jul 29 23:48:31 CEST 2008


On Jul 29, 1:30 pm, Carl Banks <pavlovevide... at gmail.com> wrote:
> On Jul 29, 5:15 am, Heiko Wundram <modeln... at modelnine.org> wrote:
>
> > I can't dig up a simple example from code I wrote quickly, but because of the
> > fact that explicit comparisons always hamper polymorphism
>
> I'm not going to take your word for it.  Do you have code that
> demonstrates how "if x" improves polymorphism relative to simple
> explicit tests?
>
> Carl Banks

I think "if x" works in some cases, but not in others.  Here's an
example I just made up and a counterexample from my own code.

Example:

Here's a function, print_members.  It's just something that takes some
iterable and prints its members in XML.  It's special-cased so that an
empty iterable gets an empty tag.  (This is a bit of a trivial
example, I admit; the point is that the empty iterable is a special
case.)

def print_members(iterable):
    if not iterable:
        print '<members />'
        return
    print '<members>'
    for item in iterable:
        print '<item>%s</item>' % item
    print '</members>'

>>> print_members(['a', 'b', 'c'])

<members>
<item>a</item>
<item>b</item>
<item>c</item>
</members>

>>> print_members([])

<members />

I could have used "if len(iterable) == 0:" for these cases, but only
because the length of a list means something.  Say I were to implement
a matrix class.  The length of a matrix isn't well-defined, so I won't
implement __len__.  I will, however, implement __nonzero__, since if
it has no members, it's empty.

class SimpleMatrix(object):

    def __init__(self, *rows):
        self.rows = rows

    def __nonzero__(self):
        for row in self.rows:
            if row:
                return True
        return False

    def __iter__(self):
        return iter(sum(self.rows, []))

>>> a = SimpleMatrix([1, 2, 3], [4, 5, 6])
>>> print_members(a)

<members>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</members>

>>> b = SimpleMatrix([],[],[])
>>> len(b)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'SimpleMatrix' has no len()

>>> print_members(b)

<members />

So print_members can work on iterables that have no len(), and handle
the special case of an empty iterable, as long as __nonzero__ is
implemented.

Counterexample:

While "if x" works well in some circumstances, I don't like using it
for purely numeric types.  For instance, I have a mutable Signal class
for doing some basic DSP.  Among other things, I can apply a DC offset
to the signal (it just adds the offset to all the samples).  I have a
special case for an offset of 0 so I don't have to loop through all
the samples (I also have a two-pass remove_offset method that
subtracts the average; if it's already properly centred, I can skip a
step).

class Signal:
    [...]
    def dc_offset(self, amount):
        if amount == 0:
            return
        self.samples = [sample + amount for sample in self.samples]

Here, "if amount == 0" is deliberate.  At no point should I be adding
an offset that's a list or a dict, even an empty one.
Signal.dc_offset should raise an exception if I try to do that,
because that indicates there's a bug somewhere.  If I do pass in [] or
{}, that test will fail, and it will try to add the list or dict to
the samples, at which point I get a TypeError.

Geoff G-T




More information about the Python-list mailing list