开发者

Iterating over list or single element in python

开发者 https://www.devze.com 2023-03-21 02:07 出处:网络
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

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 q开发者_如何学JAVAuite 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


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.


You'll want to do the following:

iterator = (x,) if not isinstance(x, (tuple, list)) else x

then

for i in iterator:
    #do stuff


You could also try using the operator.isSequenceType function

import operator
x = unknown_function()
if not operator.isSequenceType(x) and not isinstance(x, basestring):
    x = (x,)
for item in x:
    do_something(item)


You might define a function that ensures the returned value supports iteration (str, dict, tuple, etc --including user-defined sequence types that don't directly inherit from these classes) rather than checking if it is a tuple or list directly.

def ensure_iterable(x):
    return (x,) if not hasattr(x, '__iter__') else x

x = ensure_iterable(UnknownFunction())
for i in x:
    do_something(i)


You might be able to get better performance if you use generators. This should work on python 3.3 and above.

from collections import Iterable

def iterrify(obj):
    """
    Generator yielding the passed object if it's a single element or
    yield all elements in the object if the object is an iterable.

    :param obj: Single element or iterable.
    """
    if isinstance(obj, (str, bytes)):  # Add any iterables you want to threat as single elements here
        yield obj
    elif isinstance(obj, Iterable):  # Yield from the iterables.
        yield from obj
    else:  # yield single elements as is.
        yield obj


I like the approach with Itreable suggested by someone else. Though maybe in some cases the following would be better. It is more more EAFP (https://docs.python.org/3.5/glossary.html#term-eafp) way:

In [10]: def make_iter(x): 
    ...:         try: 
    ...:             return iter(x) 
    ...:         except TypeError: 
    ...:             # We seem to be dealing with something that cannot be itereated over. 
    ...:             return iter((x,)) 
    ...:              

In [11]: make_iter(3)                                                                                                                                                                         
Out[11]: <tuple_iterator at 0x7fa367b29590>

In [13]: make_iter((3,))                                                                                                                                                                      
Out[13]: <tuple_iterator at 0x7fa367b4cad0>

In [14]: make_iter([3])                                                                                                                                                                       
Out[14]: <list_iterator at 0x7fa367b29c90>

This doesn't bothers with checking what we are dealing with. We just try to get an iterator and if it fails, we assume it failed because we were dealing with something that cannot be iterated over (well, it really seems like it cannot be). So we just make a tuple and make an iterator from that.

0

精彩评论

暂无评论...
验证码 换一张
取 消