Как проверить, является ли объект списком или кортежем (но не строкой)? - PullRequest
413 голосов
/ 02 декабря 2009

Это то, что я обычно делаю, чтобы убедиться, что входное значение равно list / tuple, но не str. Потому что много раз я сталкивался с ошибками, когда функция пропускает объект str по ошибке, а целевая функция делает for x in lst, предполагая, что lst на самом деле list или tuple.

assert isinstance(lst, (list, tuple))

Мой вопрос: есть ли лучший способ добиться этого?

Ответы [ 17 ]

323 голосов
/ 02 декабря 2009

Только в Python 2 (не Python 3):

assert not isinstance(lst, basestring)

Это на самом деле то, что вы хотите, иначе вы упустите множество вещей, которые действуют как списки, но не являются подклассами list или tuple.

168 голосов
/ 02 декабря 2009

Помните, что в Python мы хотим использовать "типизацию утки". Таким образом, все, что действует как список, может рассматриваться как список. Поэтому не проверяйте тип списка, просто посмотрите, действует ли он как список.

Но строки тоже действуют как список, и часто это не то, что мы хотим. Есть моменты, когда это даже проблема! Так что, проверяйте явно строку, но затем используйте утку.

Вот функция, которую я написал для удовольствия. Это специальная версия repr(), которая печатает любую последовательность в угловых скобках ('<', '>').

def srepr(arg):
    if isinstance(arg, basestring): # Python 3: isinstance(arg, str)
        return repr(arg)
    try:
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    except TypeError: # catch when for loop fails
        return repr(arg) # not a sequence so just return repr

Это чисто и элегантно, в целом. Но что это за isinstance() проверка делает там? Это что-то вроде хака. Но это важно.

Эта функция рекурсивно вызывает себя для всего, что действует как список. Если бы мы не обрабатывали строку специально, то она была бы обработана как список и разделена по одному символу за раз. Но тогда рекурсивный вызов попытается обработать каждый символ как список - и это сработает! Даже односимвольная строка работает как список! Функция будет продолжать вызывать себя рекурсивно, пока стек не переполнится.

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

Примечание: try / except - самый чистый способ выразить наши намерения. Но если бы этот код был как-то критичным по времени, мы могли бы заменить его каким-то тестом, чтобы увидеть, является ли arg последовательностью. Вместо того, чтобы тестировать тип, мы, вероятно, должны проверить поведение. Если у него есть метод .strip(), это строка, поэтому не считайте ее последовательностью; в противном случае, если он индексируется или итерируется, это последовательность:

def is_sequence(arg):
    return (not hasattr(arg, "strip") and
            hasattr(arg, "__getitem__") or
            hasattr(arg, "__iter__"))

def srepr(arg):
    if is_sequence(arg):
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    return repr(arg)

РЕДАКТИРОВАТЬ: Первоначально я написал выше с проверкой для __getslice__(), но я заметил, что в документации к модулю collections интересным методом является __getitem__(); это имеет смысл, вот как вы индексируете объект. Это кажется более фундаментальным, чем __getslice__(), поэтому я изменил вышесказанное.

119 голосов
/ 03 февраля 2014
H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.
63 голосов
/ 15 июня 2016

Для Python 2:

import collections

if isinstance(obj, collections.Sequence) and not isinstance(obj, basestring):
    print "obj is a sequence (list, tuple, etc) but not a string or unicode"

Для Python 3:

import collections.abc

if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str):
    print("obj is a sequence (list, tuple, etc) but not a string or unicode")

Изменено в версии 3.3: перемещены абстрактные базовые классы коллекций в модуль collection.abc. Для обратной совместимости они также будут видны в этом модуле до версии 3.8, где он перестанет работать.

31 голосов
/ 12 мая 2011

Python со вкусом PHP:

def is_array(var):
    return isinstance(var, (list, tuple))
10 голосов
/ 02 декабря 2009

Вообще говоря, тот факт, что функция, выполняющая итерации по объекту, работает как со строками, так и с кортежами и списками, является скорее функцией, чем ошибкой. Вы, конечно, можете использовать isinstance или вводить утку, чтобы проверить аргумент, но зачем вам это?

Звучит как риторический вопрос, но это не так. Ответ на вопрос «почему я должен проверять тип аргумента?» вероятно, собирается предложить решение реальной проблемы, а не предполагаемой проблемы. Почему это ошибка, когда в функцию передается строка? Кроме того: если это ошибка, когда строка передается этой функции, это также ошибка, если ей передается какая-либо иная итерация, не относящаяся к списку / кортежу? Почему или почему нет?

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

7 голосов
/ 01 января 2018

Попробуйте это для удобства чтения и лучших практик:

python2

import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
    # Do something

Python3

import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
    # Do something

Надеюсь, это поможет.

6 голосов
/ 02 декабря 2009

Объект str не имеет атрибута __iter__

>>> hasattr('', '__iter__')
False 

так что вы можете сделать чек

assert hasattr(x, '__iter__')

и это также повысит AssertionError для любого другого не повторяемого объекта.

Изменить: Как упоминает Тим ​​в комментариях, это будет работать только в Python 2.x, а не 3.x

5 голосов
/ 29 октября 2012

Это не предназначено для прямого ответа на ФП, но я хотел бы поделиться некоторыми связанными идеями.

Я был очень заинтересован в ответе @steveha выше, который, казалось, дал пример, где печать утки, кажется, ломается. Однако, подумав второй раз, его пример показывает, что типизировать утку трудно, но она не предполагает, что str заслуживает какой-либо особой обработки.

В конце концов, не str тип (например, пользовательский тип, который поддерживает некоторые сложные рекурсивные структуры) может привести к тому, что функция @steveha srepr вызовет бесконечную рекурсию. Хотя это по общему признанию довольно маловероятно, мы не можем игнорировать эту возможность. Следовательно, вместо специального случая str в srepr мы должны уточнить, что мы хотим srepr делать, когда возникает бесконечная рекурсия.

Может показаться, что один разумный подход - просто прервать рекурсию в srepr моменте list(arg) == [arg]. Это, фактически, полностью решило бы проблему с str, без каких-либо isinstance.

Однако, действительно сложная рекурсивная структура может вызвать бесконечный цикл, где list(arg) == [arg] никогда не происходит. Поэтому, хотя приведенная выше проверка полезна, ее недостаточно. Нам нужно что-то вроде жесткого ограничения на глубину рекурсии.

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

3 голосов
/ 19 мая 2017

Я нахожу такую ​​функцию с именем is_sequence в тензорном потоке .

def is_sequence(seq):
  """Returns a true if its input is a collections.Sequence (except strings).
  Args:
    seq: an input sequence.
  Returns:
    True if the sequence is a not a string and is a collections.Sequence.
  """
  return (isinstance(seq, collections.Sequence)
and not isinstance(seq, six.string_types))

И я убедился, что он соответствует вашим потребностям.

...