I would like to iterate over the outputs of an unknown function. Unfortunately I do not know whether the function returns a single item or a tuple. This must be a standard problem and there must be a standard way of dealing with this -- what I have now is quite ugly.
x = UnknownFunction()
if islist(x):
iterator = x
else:
iterator = [x]
def islist(s):
try:
len(s)
return True
except TypeError:
return False
for ii in iterator:
#do stuff
Iterating over a List is much much faster than iterating over a set. The currently accepted answer is using a very small set and list and hence, the difference is negligible there.
The most general solution to this problem is to use isinstance with the abstract base class collections.Iterable.
import collections
def get_iterable(x):
if isinstance(x, collections.Iterable):
return x
else:
return (x,)
You might also want to test for basestring as well, as Kindall suggests.
if isinstance(x, collections.Iterable) and not isinstance(x, basestring):
Now some people might think, as I once did, "isn't isinstance considered harmful? Doesn't it lock you into using one kind of type? Wouldn't using hasattr(x, '__iter__') be better?"
The answer is: not when it comes to abstract base classes. In fact, you can define your own class with an __iter__ method and it will be recognized as an instance of collections.Iterable, even if you do not subclass collections.Iterable. This works because collections.Iterable defines a __subclasshook__ that determines whether a type passed to it is an Iterable by whatever definition it implements.
>>> class MyIter(object):
... def __iter__(self):
... return iter(range(10))
...
>>> i = MyIter()
>>> isinstance(i, collections.Iterable)
True
>>> collections.Iterable.__subclasshook__(type(i))
True
It's not particularly elegant to include the code everywhere you need it. So write a function that does the massaging. Here's a suggestion I came up with for a similar previous question. It special-cases strings (which would usually be iterable) as single items, which is what I find I usually want.
def iterfy(iterable):
if isinstance(iterable, basestring):
iterable = [iterable]
try:
iter(iterable)
except TypeError:
iterable = [iterable]
return iterable
Usage:
for item in iterfy(unknownfunction()):
# do something
Update Here's a generator version that uses the new-ish (Python 3.3) yield from statement.
def iterfy(iterable):
if isinstance(iterable, str):
yield iterable
else:
try:
# need "iter()" here to force TypeError on non-iterable
# as e.g. "yield from 1" doesn't throw until "next()"
yield from iter(iterable)
except TypeError:
yield iterable
Perhaps better to use collections.Iterable to find out whether the output is an iterable or not.
import collections
x = UnknownFunction()
if not isinstance(x, collections.Iterable): x = [x]
for ii in x:
#do stuff
This will work if type of x is either of these - list, tuple, dict, str, any class derived from these.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With