Checking length of each argument - seems like I'm fighting Python

Bengt Richter bokr at oz.net
Sun Dec 4 02:06:54 EST 2005


On 3 Dec 2005 15:50:25 -0800, "Brendan" <spam4bsimons at yahoo.ca> wrote:

>There must be an easy way to do this:
>
>For classes that contain very simple data tables, I like to do
>something like this:
>
>class Things(Object):
>    def __init__(self, x, y, z):
>        #assert that x, y, and z have the same length
>
>But I can't figure out a _simple_ way to check the arguments have the
>same length, since len(scalar) throws an exception.  The only ways
>around this I've found so far are
>
>a)  Cast each to a numeric array, and check it's dimension and shape.
>This seems like far too many dependencies for a simple task:
>
>def sLen(x):
>    """determines the number of items in x.
>    Returns 1 if x is a scalar. Returns 0 if x is None
>    """
>    xt = numeric.array(x)
>    if xt == None:
>        return 0
>    elif xt.rank == 0:
>        return 1
>    else:
>        return xt.shape[0]
>
>b) use a separate 'Thing' object, and make the 'Things' initializer
>work only with Thing objects.  This seems like way too much structure
>to me.
>
>c) Don't bother checking the initializer, and wait until the problem
>shows up later.  Maybe this is the 'dynamic' way, but it seems a little
>fragile.
>
>Is there a simpler way to check that either all arguments are scalars,
>or all are lists of the same length?  Is this a poor way to structure
>things?  Your advice is appreciated

I'd go with c) unless you think errors that might result could be too mysterious
to diagnose or some dangerous action could result, but usually the errors won't be
very mysterious. If some dangerous action could result, you might well want to
impose a number of constraints, starting with checking input args, but you will
also want thorough unit tests.

Note that an assert statement gets eliminated from the generated code when optimization
is requested with a -O command line option, so you might want to write out the test and
exception raising explicitely to make sure it remains part of the code, if that is
what you want.

You could also define an external function to check that args conform
    def __init__(self, x, y, z)
        argcheck(x, y ,z) # raise exception if non-conforming

where

 >>> scalartype = (int, long, float)
 >>> def argcheck(*args):
 ...     assert len(set([isinstance(x, scalartype) and 'S' or
 ...                     hasattr(x, '__len__') and len(x) for x in args]))==1
 ...
 >>> argcheck(1, 2L, 3.0)
 >>> argcheck([1,2], [3,4], [4,5])
 >>> argcheck([1,2], [3,4], [4,5], 6)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 2, in argcheck
 AssertionError
 >>> argcheck([1,2], [3,4], [4,5,6])
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 2, in argcheck
 AssertionError
 >>> argcheck('abc','def')
 >>> argcheck('abc','def', 3)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 2, in argcheck
 AssertionError

You might want to add a check on the elements of arrays, e.g. to make sure
they are all scalartypes or all complex or whatever (and so as not to
accept str elements ;-).

Note that an assert statement disappears if optimization is called for on the python
command line with -O, so you might want to code your own test of the condition and
raise an exception explicitly if appropriate.

If the assert is enough, you can of course put it directly in the __init__, e.g.,

    def __init__(self, x, y, z)
        assert len(set([isinstance(arg, scalartype) and 'S' or
                        hasattr(arg, '__len__') and len(arg) for arg in (x,y,z)]))==1
        ...

BTW, I'm using len(set(list_of_stuff_that_should_all_be_equal))==1 to test that they are equal,
since if not, there would be more than one element in the set. So there should either be
a bunch of 'S's in the list or a bunch of lengths that are all equal, so a mix wouldn't give
one element either. But this is a lot of calling for a simple check that could be written to
short-cut lots faster for the specific case or x,y,z args, e.g., (untested)

    ii = isinstance; st = scalartype; ha = hasattr; L = '__len__' # save me typing ;-)
    def __init__(self, x, y, z):
        assert ii(x,st) and ii(y,st) and ii(z,st) or ha(x,L) and ha(y,L) and ha(z,L) and len(x)==len(y)==len(z)
        ...

If you wanted to check that all the elements of a passed vector x were scalars, you could
write (untested)
    assert sum(isinstance(_, scalartype) for _ in x)==len(x)
since True==1 as a subtype of integer.

 >>> sum(isinstance(_, scalartype) for _ in [1, 2.0, 3L])
 3
 >>> sum(isinstance(_, scalartype) for _ in [1, 2.0, 3j])
 2
 >>> sum(isinstance(_, scalartype) for _ in [1, 2.0, []])
 2
compared == len(thething) should work
Of course, your next step in using vectors might give you the check for free,
so no need for redundant checking. It's generally faster to let your args try to quack
like the ducks you need, if possible and safe.


Regards,
Bengt Richter



More information about the Python-list mailing list