Just good advice? = WAS("Re: getting system date")

Alex Martelli aleax at aleax.it
Mon May 12 03:42:45 EDT 2003


<posted & mailed>

David Broadwell wrote:
   ...
> From what you hinted at, i'll leave in my 'paranioa' tests in many of my
> routines that test things that the computer will never make a mistake on,
> but that i might.

The general idea of sanity checks is OK, and that's what the assert 
statement is for.

Applying this specifically to type checking is not wise.


> def zoobar(somelist):
>     ''' Takes a list an complains if it dosen't like it, might even give

Ask yourself, *WHY*?  What horrid fate would befall civilization as we
know it if zoobar was to be called with, e.g., an instance of array.array,
or of UserList.UserList, instead of a bona fide list?

The answer almost invariably is, nothing terrible would result: the code
would work just as well, polymorphically -- by transparently accepting
any type which is signature-polymorphical to lists, zoobar would just be
more flexible, generally usable, natural, and useful.

Occasionally, the code of zoobar may need some specific method from its
argument somelist that not all possible arguments might supply.  E.g.,
zoobar may need to clal somelist.sort().  Now that's OK if somelist is
a list, or UserList.UserList, but it would fail if somelist were an
array.array, since the latter type doesn't supply a sort method.

And, you know what?  *That's generally just fine* -- zoobar, when called
with an argument lacking the needed method .sort, will simply fail with
an AttributeError or the like when it unsuccessfully tries calling
somelist.sort() -- that's just as good as failing a microsecond earlier
with [whatever other exception -- btw, you seem not to use exceptions
in your 'paranoia tests'... you should!], more often than not.

Occasionally it's handier to fail 'as soon as possible' when the argument
type is inappropriate -- this is often easy to achieve through a little
micro-optimization, i.e., instead of e.g.

def oneway(alist):
    for x in lotsofstuff:
        alist.append(x)
    alist.sort()

which MIGHT fail at the end after appending lots of x's to alist (e.g.
when alist's an array.array and so does have an append method but not
a sort one), you can code:

def anotherway(alist):
    append = alist.append
    sort = alist.sort
    for x in lotsofstuff:
        append(x)
    sort()

this will be a little bit faster in normal successful operation AND
will fail ASAP if alist lacks either an append OR a sort method.  It's
not bomb-proof (you can't be sure that append is callable with each
of the x values, nor that sort is callable w/o arguments), but it's
quite good.  You might add some little amount of innocuous paranoia:

def anotherway2(alist):
    append = alist.append
    assert callable(append)
    sort = alist.sort
    assert callable(sort)
    for x in lotsofstuff:
        append(x)
    sort()

it costs little, it's not all that useful though (because you can
check that something is callable, but not that it's callable with
the specific arguments or lack thereof you later want to use).


For more about this, I recommend the section "Error-Checking
Strategies" in Chapter 6 ("Exceptions") of "Python in a Nutshell",
and recipe 5.10, "Checking if an Object has Necessary Attributes",
in Chapter 5, "Object-Oriented Programming", in the Python
Cookbook (the print version published by O'Reilly).  There you'll
also find out more about the acronyms LBYL and EAFP which I use
to classify error-checking strategies at this micro-level.

>     it
> back '''
>     if somelist:
>         if type(somelist) == type([]): # paranoia type tests ... bad
>         coder,
> no biscut.
>             print "good list, put code here"
>             return somelist
>         else: print "zoobar requires list types not %s" % type(somelist)
>     else: print "zoobar need argument ... a list would be good."
> 
> So, question is; in the long run, are these kind of tests worth it?

No: they employ substantial effort in order to reduce the usefulness
of a piece of code by inappropriate reliance on implementation (type)
rather than interface ("signatures").  *Code to an interface, NOT to
an implementation*.

> And; what kind are the best 'parnaoia' tests to do?

Whether some object has a required attribute (often best: get the
attribute, so an AttributeError gets raised on failure, and if no
exception occurred use the attribute value you got); whether something
is callable [callable(x) -- far from ideal but often useful]; and
whether something is (e.g.) indexable, sliceable, stringlike, numberish,
by such code snippets as:

    something[0]

    something[0:0]

    something + ''

    something + 0

sometimes in the try clause of a try/except statement if you want to
emit very specific diagnostics in case of failure (but often just
letting the resulting exception, if any, propagate, is best).  The
assert statement is a good way to demarcate "paranoia checks".

But almost invariably the RIGHT way to be paranoid is to check up
on signatures (interface), NOT on types (implementation detail).


Alex





More information about the Python-list mailing list