Typically, strings (plain and unicode) are the only iterables that you want to nevertheless consider as "single elements" -- the basestring
builtin exists SPECIFICALLY to let you test for either kind of strings with isinstance
, so it's very UN-grotty for that special case;-).
So my suggested approach for the most general case is:
if isinstance(input, basestring): input = [input]
else:
try: iter(input)
except TypeError: input = [input]
else: input = list(input)
This is THE way to treat EVERY iterable EXCEPT strings as a list directly, strings and numbers and other non-iterables as scalars (to be normalized into single-item lists).
I'm explicitly making a list out of every kind of iterable so you KNOW you can further on perform EVERY kind of list trick - sorting, iterating more than once, adding or removing items to facilitate iteration, etc, all without altering the ACTUAL input list (if list indeed it was;-). If all you need is a single plain for
loop then that last step is unnecessary (and indeed unhelpful if e.g. input is a huge open file) and I'd suggest an auxiliary generator instead:
def justLoopOn(input):
if isinstance(input, basestring):
yield input
else:
try:
for item in input:
yield item
except TypeError:
yield input
now in every single one of your functions needing such argument normalization, you just use:
for item in justLoopOn(input):
You can use an auxiliary normalizing-function even in the other case (where you need a real list for further nefarious purposes); actually, in such (rarer) cases, you can just do:
thelistforme = list(justLoopOn(input))
so that the (inevitably) somewhat-hairy normalization logic is just in ONE place, just as it should be!-)