Итерирование по списку или одному элементу в python

Я хотел бы перебрать выходы неизвестной функции. К сожалению, я не знаю, возвращает ли функция один элемент или кортеж. Это должна быть стандартная проблема, и должен быть стандартный способ справиться с этим - что у меня сейчас довольно уродливое.

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

Ответы

Ответ 1

Наиболее общим решением этой проблемы является использование isinstance с абстрактным базовым классом collections.Iterable.

import collections

def get_iterable(x):
    if isinstance(x, collections.Iterable):
        return x
    else:
        return (x,)

Вы также можете протестировать и для basestring, как предлагает Kindall.

    if isinstance(x, collections.Iterable) and not isinstance(x, basestring):

Теперь некоторые люди могут подумать, как я когда-то, "не isinstance считается вредным? Он не блокирует вас в использование одного типа типа? Не использовал бы hasattr(x, '__iter__') лучше?"

Ответ: не тогда, когда речь идет об абстрактных базовых классах. Фактически, вы можете определить свой собственный класс с помощью метода __iter__, и он будет распознан как экземпляр collections.Iterable, даже если вы не подклассы collections.Iterable. Это работает, потому что collections.Iterable определяет __subclasshook__, который определяет, является ли переданный ему тип Iterable любым определением, которое он реализует.

>>> class MyIter(object):
...     def __iter__(self):
...         return iter(range(10))
... 
>>> i = MyIter()
>>> isinstance(i, collections.Iterable)
True
>>> collections.Iterable.__subclasshook__(type(i))
True

Ответ 2

Не особенно элегантно включать код везде, где он вам нужен. Поэтому напишите функцию, которая делает массаж. Вот предложение, которое я придумал для аналогичного предыдущего вопроса. Это строки специальных случаев (которые обычно будут итерируемыми) как отдельные элементы, и это то, что я обычно нахожу.

def iterfy(iterable):
    if isinstance(iterable, basestring):
        iterable = [iterable]
    try:
        iter(iterable)
    except TypeError:
        iterable = [iterable]
    return iterable

Использование:

for item in iterfy(unknownfunction()):
     # do something

Ответ 3

Вы хотите сделать следующее:

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

затем

for i in iterator:
    #do stuff

Ответ 4

Возможно, лучше использовать collections.Iterable, чтобы узнать, является ли вывод итерабельным или нет.

import collections

x = UnknownFunction()
if not isinstance(x, collections.Iterable): x = [x]

for ii in x:
    #do stuff

Это будет работать, если тип x является одним из следующих: list, tuple, dict, str, любой класс, полученный из них.

Ответ 5

Вы также можете попробовать использовать функцию operator.isSequenceType

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

Ответ 6

Вы можете определить функцию, которая гарантирует, что возвращаемое значение поддерживает итерацию (str, dict, tuple и т.д.), включая пользовательские типы последовательностей, которые непосредственно не наследуются от этих классов), а не проверку если это непосредственно tuple или list.

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

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

Ответ 7

Возможно, вы сможете добиться лучшей производительности, если будете использовать генераторы. Это должно работать на Python 3.3 и выше.

from collections import Iterable

def iterrify(obj):
    """
    Generator yielding the passed object if it 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