Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating over list or single element in python

Tags:

python

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
like image 890
Edward Avatar asked Jul 15 '11 17:07

Edward


People also ask

Is it faster to iterate through list or set Python?

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.


3 Answers

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
like image 163
senderle Avatar answered Oct 25 '22 16:10

senderle


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
like image 44
kindall Avatar answered Oct 25 '22 16:10

kindall


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.

like image 32
Pushpak Dagade Avatar answered Oct 25 '22 15:10

Pushpak Dagade