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 -- http://mail.python.org/mailman/listinfo/python-list