Newbie - How to iterate list or scalar ?

Bruno Desthuilliers onurb at xiludom.gro
Tue Aug 8 06:50:19 EDT 2006


Andy Dingley wrote:
> I seem to be writing the following fragment an awful lot, and I'm sure
> it's not the best and Pythonic way to do things. Any advice on better
> coding style?
> 
> pluginVersionNeeded is a parameter passed into a method and it can
> either be a simple scalar variable, or it can be a list of the same
> variables.  My problem is how to iterate it, whether it's a a list or
> not.
> 
> I can't simply throw it into the for...in loop -- if the scalar
> variable were to be a string (which is considered to be atomic in my
> application) then Python sees this string as iterable and iterates over
> the characters in it!

Yes, this is a common gotcha. Problem is that, as someone here noticed
sometimes ago, what should be a scalar and what should be an iterable is
often application or context dependent... And according to the context,
string being iterable can be either a royal PITA or the RightThing(tm).


> 
>     versionsNeeded = pluginVersionNeeded
>     if isinstance( versionsNeeded, list):
>         versionsToTest = versionsNeeded
>     else:
>         versionsToTest = [ versionsNeeded ]
>     for versionNeeded in versionsToTest:
>         pass

<OT>
Too much names essentially refering to the same thing IMHO... this could
be simplified to:

    if not isinstance(pluginVersionNeeded, list):
        pluginVersionNeeded = [pluginVersionNeeded]
    for versionNeeded in pluginVersionNeeded:
        pass
</OT>

The second problem here is that, given the above (I mean in your
original snippet) use of versionsToTest, there's really no reason to
assume it should be a list - any iterable could - and IMHO should - be
accepted... expect of course for strings (royal PITA case, duh).

AS far as I'm concerned, I'd rather either:

1/ test versionsToTest against basestring (which is the parent class for
both strings and unicode) and turn it into a list if needed

     if isinstance( pluginVersionsNeeded, basestring):
         pluginVersionsNeeded = [pluginVersionsNeeded]
     for versionNeeded in pluginVersionsNeeded:
         pass

The problem is that it limits 'scalars' to strings (and unicodes), and
will fail with say ints or floats etc...

This could be handled with a tuple of 'to-be-considered-as-scalar' types:
     scalars = (basestring, int, float)
     if isinstance( pluginVersionsNeeded, scalars):
         pluginVersionsNeeded = [pluginVersionsNeeded]
     for versionNeeded in pluginVersionsNeeded:
         pass

2/ test for pluginVersionsNeeded.__iter__ (an attribute of most
iterables except strings...):

     if not hasattr(pluginVersionsNeeded, '__iter__'):
         pluginVersionsNeeded = [pluginVersionsNeeded]
     for versionNeeded in pluginVersionsNeeded:
         pass

It's probably the most general scheme, but won't work if you intend to
consider tuples as scalars since tuples have the __iter__ attribute.


Also, if this is a common pattern in your module, you perhaps want to
abstract this out (example for both of the above solutions):

1/
def enumerateVersion(versions, scalars=(basestring, int, float)):
    if isinstance(versions, scalars):
        yield versions
        raise StopIteration # not required but clearer IMHO
    else:
        for version in versions:
            yield version

NB : the default arg 'scalars' let you customize what is to be
considered as a scalar...

2/
def enumerateVersion(versions):
    if hasattr(versions, '__iter__'):
        for version in versions:
            yield version
        raise StopIteration # not required but clearer IMHO
    else:
        yield versions


and in the calling code:
    ...
    for versionNeeded in enumerateVersions(pluginVersionNeeded):
        do_whatever_with(versionNeeeded)
    ...



HTH



More information about the Python-list mailing list