Как проверить, является ли объект списком или кортежем (но не строкой)?
Вопрос
Это то, что я обычно делаю, чтобы убедиться, что входные данные являются 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