Як перевірити, чи об’єкт є списком чи кортежем (але не рядком)?


444

Це те, що я зазвичай роблю для того, щоб переконатися, що вхід є list/ tuple-, але не є str. Тому що я багато разів натрапляв на помилки, коли функція strпомилково передає об'єкт, а цільова функція робить for x in lstприпущення, що lstце насправді є listабо tuple.

assert isinstance(lst, (list, tuple))

Моє запитання: чи є кращий спосіб досягти цього?


9
type (lst) - список?
jackalope

1
не єречовина (ключ, шість. строків_типів)
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 пройшов би цей чек)
Пітер Гібсон,

11
@PeterGibson - Комбінація двох забезпечить дійсну, більш обмежувальну перевірку та забезпечить 1) lst є ітерабельним, 2) lst не є рядком. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA

4
Ну, це рішення перевіряє лише рядкові похідні типи, а як щодо цілих чисел, парних чи будь-якого іншого типу, який не можна повторити?
Енеко Алонсо

171

Пам’ятайте, що в 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()там чекає? Це свого роду хак. Але це істотно.

Ця функція рекурсивно викликає все, що діє як список. Якщо ми не обробляли рядок спеціально, то вона буде розглядатися як список і розділяти по одному символу за один раз. Але тоді рекурсивний виклик спробував би розглянути кожного символу як список - і він би спрацював! Навіть односимвольна рядок працює як список! Функція продовжувала б викликати себе рекурсивно, поки стек не переповнюється.

Функції, такі як ця, що залежать від кожного рекурсивного виклику, що розбиває роботу, яку потрібно виконати, повинні мати рядки спеціального регістру - тому що ви не можете розбити рядок нижче рівня односимвольної струни та навіть однієї -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)

EDIT: Спочатку я писав вище з чеком, __getslice__()але помітив, що в collectionsдокументації на модуль цікавий метод __getitem__(); це має сенс, саме так ви індексуєте об’єкт. Це здається більш принциповим, ніж __getslice__()я змінив вище.


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__")))
MiniQuark

124
H = "Hello"

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

11
За винятком того, що він не використовує ідіому Python, що стосується введення качок, як це вказували інші коментатори (хоча це питання відповідає прямо і чисто).
Soren Bjornstad

7
Ця відповідь менш прийнятна, ніж інші, тому що вона не дозволяє вводити качок, а також не вдається в простому випадку підкласингу (типовим прикладом є клас namedtuple).
Філіп Готьє

11
"Не допускаючи набору качки" не робить відповідь менш прийнятною, особливо з огляду на те, що ця відповідь насправді відповідає на питання.
Петрі

4
Я відповів на цю відповідь, але if isinstance( H, (list, tuple) ): ...він коротший і зрозуміліший.
shahar_m

2
Альтернативний синтаксис:if type(H) in [list, tuple]:
Štefan Schindler

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"

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 вам потрібна, а неречовина (obj, bytes) ... якщо ви хочете список речей, а не просто перерахувати байти ...
Ерік Аронесті

35

Пітон з ароматом PHP:

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

6
Python - мова, набрана качкою, тому ви дійсно повинні перевірити, чи має атрибут var __getitem__. Також назва вводить в оману, оскільки є також модуль масиву. І var також може бути numpy.ndarray або будь-який інший тип, який має __getitem__. Дивіться stackoverflow.com/a/1835259/470560 для правильної відповіді.
peterhil

9
@peterhil strтакож має __getitem__ваш чек не виключаєstr
erikbwork

9
Так і диктує. Тут перевірка __getitem__поганих порад.
Петрі

10

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

Це звучить як риторичне питання, але це не так. Відповідь на "чому я повинен перевірити тип аргументу?" ймовірно, запропонує вирішити справжню проблему, а не сприйняту проблему. Чому це помилка, коли рядок передається функції? Також: якщо це помилка, коли рядок передається цій функції, чи це також помилка, якщо до неї передається якийсь інший неперелік / кортеж, ітерабельний? Чому чи чому б ні?

Я думаю, що найпоширенішою відповіддю на це питання, ймовірно, є те, що розробники, які пишуть f("abc"), очікують, що функція буде вести себе так, ніби вони написали f(["abc"]). Ймовірно, є обставини, коли має сенс захищати розробників від самих себе, ніж це підтримує випадок ітерації використання символів у рядку. Але я б спочатку довго і важко подумав над цим.


16
"Але я б спочатку довго і важко подумав над цим". Я б не став. Якщо функція повинна бути функцією list-y, то так, вона має ставитися до них однаково (тобто, задавши список, виплюнути його назад, подібні речі). Однак, якщо це функція, де одним з аргументів може бути або рядок, або список рядків (що є досить загальною потребою), тоді примушування розробника за допомогою цієї функції завжди вводити їх параметр всередині масиву здається трохи набагато . Крім того, подумайте, як би ви обробляли, скажімо, введення JSON. Ви обов'язково хочете обробити списки об'єктів, відмінних від рядка.
Jordan Reiter

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'
Juha Untinen

1
У Python 3 це: from typing import List-> isinstance([1, 2, 3], List= True, і isinstance("asd", List)= False
Juha Untinen

5

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

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

тому ви можете зробити перевірку

assert hasattr(x, '__iter__')

і це також призведе до приємного AssertionErrorдля будь-якого іншого об'єкта, який не можна повторити.

Редагувати: Як згадує Тім у коментарях, це працюватиме лише в python 2.x, а не 3.x


8
Обережно: в Python 3 hasattr('','__iter__')повертається True. І це, звичайно, має сенс, оскільки ви можете перебирати рядок.
Тім Піцкер

1
Дійсно? Я цього не знав. Я завжди думав, що це елегантне рішення проблеми, ну добре.
Moe

1
Цей тест не працював на pyodbc.Row. У нього немає iter __ (), але він більш-менш поводиться як список (він навіть визначає "__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 в 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))

І я переконався, що він відповідає вашим потребам.


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

Не перевірений на генераторах, я думаю, що ви залишаєтесь при наступному «виході», якщо його передадуть в генератор, який може накрутити речі вниз за течією. Але знову ж таки, це "єдиний тест"


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це список (а не кортеж ). Ось чому призначення виконується таким чином. Можливо, хтось може пролити світло на те, чому це так.


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

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і для всіх видів ітерабельних об'єктів.


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
Ласкаво просимо до SO. Пояснення, чому цей код відповідає на питання, було б корисним.
Нік

Так, звичайно, я використовую методи, подібні до цього, оскільки труба трактується як або так, ви стверджуєте, що тип повинен бути списком або типом кортежу, що виводить власну помилку повідомлення для обробки помилок. Я вважаю, що це відповідає на запитання, але мені було цікаво, ніби це ефективний спосіб зробити це, оскільки я все ще намагаюся отримати вигляд написання найбільш оптимізованого коду. Однак я не впевнений, якщо цей код не вистачає речей, які можуть діяти як списки / кортежі, але не є підкласами жодного з них, як те, як прийнята відповідь враховує таку можливість. Дякую!
Йорж

-1

Просто роби це

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

5
isin substance (lst, (список, кортеж))
Davi Lima

@DaviLima ОК, це інший спосіб. Але тип () рекомендований для вбудованих типів і єречовин для Класів.
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

5
Ви майже ніколи не повинні цього робити. Що станеться, якщо я some_varє екземпляром класу, який є підкласом list()? Ваш код не матиме уявлення, що з ним робити, навіть якщо він буде ідеально працювати під кодом "зробити щось зі списком". І вам рідко потрібно дбати про різницю між списком і кортежем. Вибачте, -1.
steveha

1
Не потрібно писати type(tuple())- tupleобійдеться. Те саме для списку. Крім того, strі unicodeрозширення basestring, що є реальним типом рядка, тому ви хочете перевірити це натомість.
ставитеся до своїх модників добре

@DrBloodmoney: Випадковий поворот. Будь-ласка (редакційно) відредагуйте свою відповідь, щоб дозволити мені зняти протокол.
SabreWolfy

Рівність не здається мені важливим порівнянням для типів. Я б перевірити ідентичність замість: type(i) is list. Крім того, type(list())це лише listсаме ... Нарешті, це не витончено працює з підкласами. Якщо iнасправді і OrdersDict, або якийсь з іменем nametuple, цей код трактується як рядок.
bukzor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.