Andy said:
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