Итерирование по списку или одному элементу в 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