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

но опять же, в общем, ваш метод прост и правилен, что звучит хорошо для меня!