pythonic способ конвертировать переменную в список - PullRequest
15 голосов
/ 13 сентября 2009

У меня есть функция, чьим входным аргументом может быть элемент или список элементов. Если этот аргумент является единственным элементом, я помещаю его в список, чтобы можно было последовательно перебирать входные данные.

В настоящее время у меня есть это:

def my_func(input):
    if not isinstance(input, list): input = [input]
    for e in input:
        ...

Я работаю с существующим API, поэтому не могу изменить входные параметры. Использование isinstance () кажется хакерским, поэтому есть ли правильный способ сделать это?

Ответы [ 8 ]

13 голосов
/ 13 сентября 2009

Как правило, строки (plain и unicode) являются единственными итерациями, которые вы, тем не менее, хотите рассматривать как «отдельные элементы» - встроенная функция basestring существует ОСОБЕННО, чтобы позволить вам тестировать строки любого типа с isinstance, поэтому это очень непонятно для этого особого случая; -).

Итак, мой предложенный подход для наиболее общего случая:

  if isinstance(input, basestring): input = [input]
  else:
    try: iter(input)
    except TypeError: input = [input]
    else: input = list(input)

Это способ ОБРАБОТАТЬ КАЖДЫЕ итерируемые ИСКЛЮЧИТЕЛЬНЫЕ строки как список напрямую, строки и числа и другие не итерируемые как скаляры (для нормализации в списки из одного элемента).

Я явно делаю список из всех видов итераций, поэтому вы ЗНАЕТЕ, что в дальнейшем вы сможете выполнять КАЖДЫЙ трюк со списком - сортировку, повторение более одного раза, добавление или удаление элементов для упрощения итерации и т. Д., Все без изменения АКТУАЛЬНЫЙ входной список (если список действительно был ;-). Если все, что вам нужно, это один простой цикл 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))

так, чтобы (неизбежно) логика нормализации с некоторой шероховатостью была в ОДНОМ месте, как и должно быть! -)

9 голосов
/ 13 сентября 2009

Мне нравится предложение Андрея Ваджны о 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
3 голосов
/ 13 сентября 2009

Во-первых, не существует общего метода, который мог бы отличить «один элемент» от «списка элементов», поскольку по определению список может быть элементом другого списка.

Я бы сказал, что вам нужно определить, какие типы данных у вас могут быть, чтобы вы могли иметь:

  • любой потомок 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]
2 голосов
/ 13 сентября 2009

Вы можете поставить * перед аргументом, таким образом вы всегда получите кортеж:

def a(*p):
  print type(p)
  print p

a(4)
>>> <type 'tuple'>
>>> (4,)

a(4, 5)
>>> <type 'tuple'>
>>> (4,5,)

Но это заставит вас вызывать вашу функцию с переменными параметрами, я не знаю, приемлемо ли это для вас.

1 голос
/ 13 сентября 2009

Вы можете выполнять прямые сравнения типов, используя type().

def my_func(input):
    if not type(input) is list:
        input = [input]
    for e in input:
        # do something

Однако способ, которым вы его используете, позволит любому типу , производному от типа list, проходить через. Таким образом предотвращая случайное завершение любых производных типов.

0 голосов
/ 13 сентября 2009

Это хороший способ сделать это (не забудьте включить кортежи).

Тем не менее, вы также можете подумать, имеет ли аргумент __iter__ метод или __getitem__ метод . (обратите внимание, что строки имеют __getitem__ вместо __iter __.)

hasattr(arg, '__iter__') or hasattr(arg, '__getitem__')

Вероятно, это наиболее общее требование для типа, подобного списку, чем проверка только типа.

0 голосов
/ 13 сентября 2009

Ваш подход мне кажется правильным.

Это похоже на то, как вы используете atom? в Лиспе, когда вы перебираете списки и проверяете текущий элемент, чтобы увидеть, является ли он списком или нет, потому что, если это список, вы тоже хотите обрабатывать его элементы.

Так что, да, не вижу в этом ничего плохого.

0 голосов
/ 13 сентября 2009

Это кажется разумным способом сделать это. Вы хотите проверить, является ли элемент списком, и это выполняется непосредственно. Это усложняется, если вы хотите поддерживать и другие «похожие на список» типы данных, например:

isinstance(input, (list, tuple))

или, в более общем плане, абстрагируйся от вопроса:

def iterable(obj):
  try:
    len(obj)
    return True
  except TypeError:
    return False

но, в заключение, ваш метод прост и корректен, что звучит хорошо для меня!

...