Как проверить, является ли объект списком или кортежем (но не строкой)?

StackOverflow https://stackoverflow.com/questions/1835018

  •  11-09-2019
  •  | 
  •  

Вопрос

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

assert isinstance(lst, (list, tuple))

Мой вопрос заключается в следующем:есть ли лучший способ достичь этого?

Это было полезно?

Решение

Только на python 2 (не на python 3):

assert not isinstance(lst, basestring)

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

Другие советы

Помните, что в 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__() поэтому я изменил вышесказанное.

H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.

Для 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")

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

Для 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 со вкусом PHP:

def is_array(var):
    return isinstance(var, (list, tuple))

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

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

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

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

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

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

Тот Самый str объект не имеет __iter__ атрибут

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

так что вы можете провести проверку

assert hasattr(x, '__iter__')

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

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

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

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

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

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

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

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

Я нахожу такую функцию с именем is_sequence в tensorflow.

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))

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

Я делаю это в своих тестовых примерах.

def assertIsIterable(self, item):
    #add types here you don't want to mistake as iterables
    if isinstance(item, basestring): 
        raise AssertionError("type %s is not iterable" % type(item))

    #Fake an iteration.
    try:
        for x in item:
            break;
    except TypeError:
        raise AssertionError("type %s is not iterable" % type(item))

Непроверенный на генераторах, я думаю, вы остаетесь при следующем "выходе", если передадите его в генератор, что может испортить ситуацию ниже по течению.Но опять же, это "единичный тест"

самый простой способ...используя any и isinstance

>>> console_routers = 'x'
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
False
>>>
>>> console_routers = ('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True
>>> console_routers = list('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True

В манере "утиного набора текста", как насчет

try:
    lst = lst + []
except TypeError:
    #it's not a list

или

try:
    lst = lst + ()
except TypeError:
    #it's not a tuple

соответственно.Это позволяет избежать isinstance / hasattr занятия самоанализом.

Вы также можете проверить наоборот:

try:
    lst = lst + ''
except TypeError:
    #it's not (base)string

Все варианты фактически не изменяют содержимое переменной, но подразумевают переназначение.Я не уверен, может ли это быть нежелательным при некоторых обстоятельствах.

Интересно, что с заданием "на месте" += НЕТ TypeError будет повышен в любом случае, если lst является Список (не является кортеж).Вот почему задание выполняется таким образом.Может быть, кто-нибудь сможет пролить свет на то, почему это так.

Просто сделай это

if type(lst) in (list, tuple):
    # Do stuff

В Python 3 есть это:

from typing import List

def isit(value):
    return isinstance(value, List)

isit([1, 2, 3])  # True
isit("test")  # False
isit({"Hello": "Mars"})  # False
isit((1, 2))  # False

Таким образом, чтобы проверить наличие как Списков, так и Кортежей, было бы:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"

Я склонен делать это (если мне действительно, очень нужно).:

for i in some_var:
   if type(i) == type(list()):
       #do something with a list
   elif type(i) == type(tuple()):
       #do something with a tuple
   elif type(i) == type(str()):
       #here's your string
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top