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

444

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

assert isinstance(lst, (list, tuple))

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

Шридхар Ратнакумар
источник
9
тип (lst) это список?
шакалоп
1
не isinstance (ключ, six.string_types)
wyx

Ответы:

332

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

assert not isinstance(lst, basestring)

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

Ник Крейг-Вуд
источник
91
Да, это правильный ответ. В Python 3 basestringуже нет, и вы просто проверьте isinstance(lst, str).
Steveha
5
Есть много вещей, которые вы можете перебирать, например списки, например set, выражения генератора, итераторы. Есть экзотические вещи, такие как mmapменее экзотические, arrayкоторые похожи на списки, и, возможно, многое другое я забыл.
Ник Крейг-Вуд
50
Стоит отметить, что это не гарантирует lstповторяемость, в то время как оригинал (например, int прошел бы эту проверку)
Peter Gibson
11
@PeterGibson - комбинация этих двух элементов обеспечит правильную, более строгую проверку и обеспечит 1) lst итеративность, 2) lst не строка. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA
4
Ну, это решение проверяет только строковые производные типы, но как насчет целых, двойных или любых других не повторяемых типов?
Энеко Алонсо
171

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

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

Вот функция, которую я написал для удовольствия. Это специальная версия, 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()делает эта проверка? Это что-то вроде хака. Но это важно.

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

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

Примечание: 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__()я изменил выше.

steveha
источник
2
@stantonk, спасибо, что сказал это, но я думаю, что когда я написал это, уже был принят принятый ответ, и я не ожидаю, что принятый ответ будет изменен.
Steveha
@steveha: sreprочень интересная идея. Но я придерживаюсь мнения, отличного от вас, о том, нужно ли это в особом случае str. Да, strбезусловно, самая очевидная и распространенная итерация, которая может вызвать бесконечную рекурсию в srepr. Но я легко могу представить пользовательские итерации, которые ведут себя одинаково (с уважительной причиной или без нее). Вместо особого случая strмы должны признать, что этот подход может столкнуться с бесконечной рекурсией, и согласиться на какой-то способ борьбы с ним. Я отправлю свое предложение в ответ.
максимум
1
Я думаю, что это определенно правильный путь. Однако, чтобы разобраться с особым случаем (со строкой в ​​этом сценарии), я думаю, что нам лучше задать вопрос "как человек скажет разницу?" Например, рассмотрим аргумент функции, который может быть списком адресов электронной почты или одним адресом электронной почты (помните, что строка - это просто список символов). Дайте эту переменную человеку. Как можно сказать, что это? Самый простой способ, который я могу придумать, это посмотреть, сколько символов в каждом элементе списка. Если оно больше 1, аргумент определенно не может быть списком символов.
Джош
1
Я немного подумал об этом и обсудил это с несколькими другими людьми, и я думаю, что srepr()все в порядке. Нам нужна рекурсивная функция для обработки таких вещей, как список, вложенный в другой список; но для строк мы предпочли бы, чтобы они печатались как, а "foo"не как <'f', 'o', 'o'>. Так что явная проверка строки имеет смысл. Кроме того, на самом деле нет других примеров типов данных, в которых итерация всегда возвращает итерацию, а рекурсия всегда вызывает переполнение стека, поэтому нам не нужно специальное свойство для проверки этого («Практичность превосходит чистоту»).
Steveha
1
Это не работает в Python 3, потому что у строк есть __iter__()метод в Python 3, но не в Python 2. Вы пропускаете круглые скобки is_sequence(), оно должно return (not hasattr(arg, "strip") and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")))
выглядеть так
124
H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.
Мани Шанкар Венканкатачалам
источник
11
За исключением того, что он не использует идиому Python для печати утки, как указали другие комментаторы (хотя он отвечает на вопрос прямо и четко).
Сорен Бьорнстад
7
Этот ответ менее приемлем, чем другие, потому что он не допускает типизацию утки, а также дает сбой в простом случае создания подклассов (типичным примером является класс namedtuple).
Филипп Готье
11
«Не допускать утки» не делает ответ менее приемлемым, особенно учитывая, что этот ответ действительно отвечает на вопрос.
Петри
4
Я проголосовал за этот ответ, но if isinstance( H, (list, tuple) ): ...он короче и понятнее.
shahar_m
2
Альтернативный синтаксис:if type(H) in [list, tuple]:
Штефан Шиндлер
77

Для 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: перемещены абстрактные базовые классы коллекций в модуль collection.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"
suzanshakya
источник
5
Вот Это Да! Это работает очень хорошо, и гораздо кратче, чем любой другой правильный ответ. Я понятия не имел, что встроенные типы наследуют, collections.Sequenceно я проверил это, и я вижу, что они делают. Так и делает xrange. Еще лучше, этот тест исключает dict, который имеет __getitem__и __iter__.
Нил Мэйхью
Любая идея, почему результат inspect.getmro(list)не включает в себя, Sequenceхотя? Как мы должны выяснить, что мы можем сделать, isinstanceкогда getmroне показывает все?
Стив Йоргенсен,
Порядок разрешения метода @SteveJorgensen определяет путь поиска класса, используемый Python для поиска правильного метода для использования в классах. Sequenceэто абстрактный класс.
suzanshakya
3
В Python3 вы можете заменить isinstance (obj, basestring) на isinstance (obj, str), и это должно работать.
Адриан Кейстер
2
в Python 3 вам нужен а не isinstance (obj, bytes) ... если вы хотите список вещей, а не просто перечислять байты ...
Erik Aronesty
35

Python со вкусом PHP:

def is_array(var):
    return isinstance(var, (list, tuple))
Cesar
источник
6
Python - это типизированный язык, поэтому вам действительно следует проверить, имеет ли var атрибут __getitem__. Также название вводит в заблуждение, так как есть также модуль массива. И переменная также может быть numpy.ndarray или любой другой тип, который имеет __getitem__. См. Stackoverflow.com/a/1835259/470560 для правильного ответа.
Петерхил
9
@peterhil strтакже имеет __getitem__поэтому ваш чек не исключаетstr
erikbwork
9
Как и диктат. Проверка здесь __getitem__- плохой совет.
Петри
10

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

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

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

Роберт Россни
источник
16
«Но я бы сначала долго и усердно об этом думал». Я бы не стал. Если предполагается, что эта функция является функцией list-y, то да, она должна обращаться с ними одинаково (т. Е. Получить список, выплюнуть его назад и тому подобное). Однако, если это функция, в которой один из аргументов может быть либо строкой, либо списком строк (что является довольно распространенной необходимостью), то принуждение разработчика, использующего эту функцию, всегда вводить свой параметр внутри массива, кажется немного большим , Кроме того, подумайте, как вы будете обрабатывать, скажем, ввод JSON. Вы определенно хотели бы обработать списки объектов, отличных от строки.
Джордан Рейтер
8

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

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

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

Ом Пракаш
источник
Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Юха Унтинен
1
В Python 3 это: from typing import List-> isinstance([1, 2, 3], List= Trueи isinstance("asd", List)= False
Юха Унтинен
5

У strобъекта нет __iter__атрибута

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

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

assert hasattr(x, '__iter__')

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

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

Моу
источник
8
Осторожно: в Python 3 hasattr('','__iter__')возвращается True. И, конечно, это имеет смысл, поскольку вы можете перебирать строки.
Тим Пицкер
1
В самом деле? Я этого не знал. Я всегда думал, что это было элегантное решение проблемы, да ладно.
Мо
1
Этот тест не работал на pyodbc.Row. У него нет итератора __ (), но он более или менее ведет себя как список (он даже определяет «__setitem »). Вы можете итерировать его элементы просто отлично. Функция len () работает, и вы можете индексировать ее элементы. Я изо всех сил пытаюсь найти правильную комбинацию, которая ловит все типы списка, но исключает строки. Я думаю, что я согласен на проверку " getitem " и " len ", явно исключая basestring.
haridsv
5

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

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

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

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

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

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

Максимум
источник
1
Хм, мне нравится, как ты думаешь. Я думаю, вы не можете утверждать, что мой код практичен: есть только один общий случай str, который обрабатывает код специального случая. Но, может быть, должно появиться новое стандартное свойство, которое код может проверять, .__atomic__скажем, которое сигнализирует о том, что что-то нельзя сломать дальше. Возможно, уже слишком поздно, чтобы добавить еще одну встроенную функцию atomic()в Python, но, возможно, мы можем добавить from collections import atomicили что-то еще.
Steveha
5

Я нахожу такую ​​функцию с именем 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))

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

Лернер Чжан
источник
2

Я делаю это в моих тестовых случаях.

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

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

FlipMcF
источник
2

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

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это список (не кортеж ). Вот почему назначение сделано таким образом. Может быть, кто-то может пролить свет на то, почему это так.

utobi
источник
1

самый простой способ ... используя 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
abarik
источник
1

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

Строковое представление строковых объектов - это сама строка, поэтому вы можете проверить, получаете ли вы равный объект из strконструктора:

# If a string was passed, convert it to a single-element sequence
if var == str(var):
    my_list = [var]

# All other iterables
else: 
    my_list = list(var)

Это должно работать для всех объектов, совместимых с strи для всех видов итерируемых объектов.

stevepastelan
источник
0

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)
Юха Унтинен
источник
0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
Ерш
источник
2
это хороший способ сделать это?
Ерш
1
Добро пожаловать в ТАК. Было бы полезно объяснить, почему этот код отвечает на вопрос.
Ник
Да, конечно, я использую методы, подобные этому, так как канал обрабатывается как или вы утверждаете, что тип должен быть списком или типом кортежем, выводящим пользовательскую ошибку сообщения для обработки ошибок. Я полагаю, что это отвечает на вопрос, но мне было любопытно, как будто это эффективный способ сделать это, поскольку я все еще пытаюсь научиться писать наиболее оптимизированный код. Я не уверен, однако, если этот код пропускает вещи, которые могут действовать как списки / кортежи, но не являются подклассами того и другого, как то, как принятый ответ обращается к этой возможности. Спасибо!
Ерш
-1

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

if type(lst) in (list, tuple):
    # Do stuff
ATOzTOA
источник
5
isinstance (lst, (список, кортеж))
Дэви Лима
@DaviLima Хорошо, это другой способ. Но type () рекомендуется для встроенных типов и isinstance для классов.
ATOzTOA
-1

в питоне> 3.6

import collections
isinstance(set(),collections.abc.Container)
True
isinstance([],collections.abc.Container)
True
isinstance({},collections.abc.Container)
True
isinstance((),collections.abc.Container)
True
isinstance(str,collections.abc.Container)
False
Жан Ду
источник
2
В последней проверке вы используете тип str, а не строку. Попробуйте, isinstance('my_string', collections.abc.Container)и вы увидите, что он вернется True. Это потому, что он abc.Containerпредоставляет __contains__метод, и, конечно, у строк.
Георгий
-6

Я склонен делать это (если я действительно, действительно должен был):

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
DrBloodmoney
источник
5
Вы почти никогда не должны делать это. Что произойдет, если я some_varявляюсь экземпляром класса, который является подклассом list()? Ваш код не будет иметь никакого представления о том, что с ним делать, даже если он будет отлично работать в коде «сделать что-то со списком». И вам редко нужно заботиться о разнице между списком и кортежем. Извините, -1.
Steveha
1
Не надо писать type(tuple())- tupleсделаем. То же самое для списка. Кроме того, оба strи unicodeextension basestring, который является настоящим типом строки, так что вы хотите проверить это вместо этого.
хорошо относитесь к своим модам
@DrBloodmoney: Случайное понижение. Пожалуйста (тривиально) отредактируйте свой ответ, чтобы я мог удалить отрицательный голос.
SabreWolfy
Равенство не кажется мне значимым сравнением типов. Я бы проверить идентичность вместо: type(i) is list. Кроме того, type(list())это просто listсамо по себе ... Наконец, это не работает изящно с подклассами. Если iэто на самом деле и OrderedDict, или какой-то именованный кортеж, этот код будет обрабатываться как строка.
Букзор