Pythonic способ преобразования переменной в список
У меня есть функция, входной аргумент которой может быть либо элементом, либо списком элементов. Если этот аргумент является одним элементом, я помещаю его в список, чтобы я мог последовательно итеративно перебирать входные данные.
В настоящее время у меня есть это:
def my_func(input):
if not isinstance(input, list): input = [input]
for e in input:
...
Я работаю с существующим API, поэтому я не могу изменить входные параметры. Использование isinstance() кажется взломанным, поэтому есть ли способ сделать это?
Ответы
Ответ 1
Мне нравится предложение Андрея Вайны hasattr(var,'__iter__')
. Обратите внимание на эти результаты от некоторых типичных типов Python:
>>> hasattr("abc","__iter__")
False
>>> hasattr((0,),"__iter__")
True
>>> hasattr({},"__iter__")
True
>>> hasattr(set(),"__iter__")
True
Это имеет дополнительное преимущество в обработке строки как неистребимой строки - это серая область, так как иногда вы хотите рассматривать их как элемент, а иногда как последовательность символов.
Обратите внимание, что в Python 3 тип str
имеет атрибут __iter__
, и это не работает:
>>> hasattr("abc", "__iter__")
True
Ответ 2
Как правило, строки (plain и unicode) являются единственными итерабельными, которые вы хотите, тем не менее, рассматривать как "отдельные элементы" - встроенный встроенный basestring
метод SPECIFICALLY позволяет тестировать любые типы строк с помощью isinstance
, поэтому это очень UN-grotty для этого особого случая; -).
Поэтому мой предложенный подход для наиболее общего случая:
if isinstance(input, basestring): input = [input]
else:
try: iter(input)
except TypeError: input = [input]
else: input = list(input)
Это способ лечения КАЖДОЙ ИЕРИРОВАННОЙ строки EXCEPT как список напрямую, строки и числа, а также другие не-итерации в виде скаляров (для нормализации в списки отдельных элементов).
Я явно делаю список из всех возможных итераций, поэтому вы ЗНАЕТЕ, что можете продолжить выполнение КАЖДОГО вида трюка списка - сортировка, повторение нескольких раз, добавление или удаление элементов для облегчения итерации и т.д., без изменения список ACTUAL (если список действительно был;-). Если вам нужен только один простой цикл for
, то этот последний шаг не нужен (и действительно бесполезен, если, например, вход является огромным открытым файлом), и вместо этого я предложил бы вспомогательный генератор:
def justLoopOn(input):
if isinstance(input, basestring):
yield input
else:
try:
for item in input:
yield item
except TypeError:
yield input
теперь в каждой из ваших функций, нуждающихся в такой нормализации аргументов, вы просто используете:
for item in justLoopOn(input):
Вы можете использовать вспомогательную нормирующую функцию даже в другом случае (где вам нужен реальный список для дальнейших гнусных целей); на самом деле, в таких (более редких) случаях вы можете просто сделать:
thelistforme = list(justLoopOn(input))
так что (неизбежно) несколько волосатая логика нормализации находится только в ОДНОМ месте, как и должно быть! -)
Ответ 3
Во-первых, нет общего метода, который мог бы указывать "единственный элемент" из "списка элементов", поскольку по списку определения может быть элементом другого списка.
Я бы сказал, что вам нужно определить, какие данные у вас есть, чтобы вы могли:
- любой потомок
list
против чего-либо еще
- Тест с
isinstance(input, list)
(так что ваш пример верен)
- любой тип последовательности, за исключением строк (
basestring
в Python 2.x, str
в Python 3.x)
- Использование метакласса последовательности:
isinstance(myvar, collections.Sequence) and not isinstance(myvar, str)
- некоторый тип последовательности для известных случаев, таких как
int
, str
, MyClass
- Тест с
isinstance(input, (int, str, MyClass))
- любой итерабельный, кроме строк:
.
try:
input = iter(input) if not isinstance(input, str) else [input]
except TypeError:
input = [input]
Ответ 4
Вы можете поставить * перед своим аргументом, таким образом вы всегда получите кортеж:
def a(*p):
print type(p)
print p
a(4)
>>> <type 'tuple'>
>>> (4,)
a(4, 5)
>>> <type 'tuple'>
>>> (4,5,)
Но это заставит вас вызвать вашу функцию с переменными параметрами, я не знаю, приемлемо ли это для вас.
Ответ 5
Вы можете выполнять прямые сравнения типов с помощью type()
.
def my_func(input):
if not type(input) is list:
input = [input]
for e in input:
# do something
Однако способ, которым он у вас есть, позволит передать любой тип, полученный из типа list
. Таким образом предотвращается случайное обматывание любых производных типов.
Ответ 6
Ваш подход кажется мне прав.
Это похоже на то, как вы используете atom?
в Lisp, когда вы перебираете списки и проверяете текущий элемент, чтобы увидеть, является ли это списком или нет, потому что, если это список, который вы хотите обрабатывать своими элементами, тоже.
Итак, да, не вижу в этом ничего плохого.
Ответ 7
Это хороший способ сделать это (не забудьте включить кортежи).
Однако вы также можете подумать, есть ли у аргумента метод __iter__ или __getitem__ . (обратите внимание, что строки имеют __getitem__ вместо __iter __.)
hasattr(arg, '__iter__') or hasattr(arg, '__getitem__')
Это, вероятно, самое общее требование для типа списка, чем проверка типа.
Ответ 8
Это кажется разумным способом сделать это. Вы хотите проверить, является ли элемент списком, и это выполняется непосредственно. Это становится более сложным, если вы хотите также поддерживать другие типы данных типа "list-like", например:
isinstance(input, (list, tuple))
или в более общем плане, отвлеките вопрос:
def iterable(obj):
try:
len(obj)
return True
except TypeError:
return False
но опять же, в общем, ваш метод прост и правилен, что звучит хорошо для меня!