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