Jak sprawdzić, czy obiekt jest listą lub krotką (ale nie łańcuchem)?

444

Tak zwykle robię, aby upewnić się, że dane wejściowe to list/ tuple- ale nie str. Ponieważ wiele razy natknąłem się na błędy, w których funkcja stromyłkowo mija obiekt, a funkcja celu for x in lstprzyjmuje, że lsttak naprawdę jest to listlub tuple.

assert isinstance(lst, (list, tuple))

Moje pytanie brzmi: czy jest lepszy sposób na osiągnięcie tego?

Sridhar Ratnakumar
źródło
9
typ (lst) to lista?
jackalope
1
not isinstance (key, six.string_types)
wyx

Odpowiedzi:

332

Tylko w python 2 (nie python 3):

assert not isinstance(lst, basestring)

Jest właściwie tym, czego chcesz, w przeciwnym razie przegapisz wiele rzeczy, które działają jak listy, ale nie są podklasami listlub tuple.

Nick Craig-Wood
źródło
91
Tak, to poprawna odpowiedź. W Pythonie 3 basestringnie ma, a ty po prostu sprawdź isinstance(lst, str).
steveha,
5
Istnieje wiele rzeczy, które możesz iterować, takich jak listy, np. setWyrażenia generatora, iteratory. Są rzeczy egzotyczne mmap, mniej egzotyczne, takie arrayjak listy, i prawdopodobnie wiele więcej zapomniałem.
Nick Craig-Wood,
50
Warto zauważyć, że nie gwarantuje lstto iterowalności, podczas gdy oryginał zrobił (np. Int przeszedłby tę kontrolę)
Peter Gibson
11
@PeterGibson - Kombinacja dwóch zapewni prawidłową, bardziej restrykcyjną kontrolę i zapewni, że 1) lst jest iterowalny, 2) lst nie jest łańcuchem. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA
4
Cóż, to rozwiązanie sprawdza tylko typy wyprowadzone z ciągu, ale co z liczbami całkowitymi, podwójnymi lub innymi typami, które nie są iterowalne?
Eneko Alonso,
171

Pamiętaj, że w Pythonie chcemy używać „pisania kaczego”. Tak więc wszystko, co działa jak lista, może być traktowane jako lista. Nie sprawdzaj więc typu listy, po prostu sprawdź, czy działa ona jak lista.

Ale łańcuchy znaków również działają jak lista i często nie tego chcemy. Są chwile, kiedy jest to nawet problem! Sprawdź więc wyraźnie, czy nie ma łańcucha, ale następnie użyj wpisywania kaczki.

Oto funkcja, którą napisałem dla zabawy. Jest to specjalna wersja, repr()która drukuje dowolną sekwencję w nawiasach kątowych („<”, „>”).

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

Ogólnie rzecz biorąc, jest to czyste i eleganckie. Ale co isinstance()tam robi ta kontrola? To rodzaj włamania. Ale to jest niezbędne.

Ta funkcja wywołuje się rekurencyjnie do wszystkiego, co działa jak lista. Gdybyśmy nie traktowali łańcucha specjalnie, wówczas byłby traktowany jak lista i dzieliłby po jednym znaku na raz. Ale wtedy wywołanie rekurencyjne spróbuje traktować każdą postać jak listę - i zadziała! Nawet ciąg jednoznakowy działa jako lista! Funkcja będzie się ciągle wywoływać rekurencyjnie, aż do przepełnienia stosu.

Funkcje takie jak ta, które zależą od każdego wywołania rekurencyjnego rozkładającego pracę do wykonania, wymagają ciągów znaków specjalnych - ponieważ nie można rozbić ciągu poniżej poziomu ciągu jednego znaku, a nawet jednego -znak znaków działa jak lista.

Uwaga: try/ exceptjest najczystszym sposobem wyrażenia naszych intencji. Ale jeśli ten kod byłby w jakiś sposób krytyczny czasowo, moglibyśmy chcieć zastąpić go jakimś testem, aby sprawdzić, czy argjest to sekwencja. Zamiast testować typ, powinniśmy prawdopodobnie przetestować zachowania. Jeśli ma .strip()metodę, jest to ciąg znaków, więc nie traktuj jej jako sekwencji; w przeciwnym razie, jeśli jest indeksowalny lub iterowalny, jest to sekwencja:

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)

EDYCJA: Pierwotnie napisałem powyższe z zaznaczeniem, __getslice__()ale zauważyłem, że w collectionsdokumentacji modułu ciekawą metodą jest __getitem__(); ma to sens, tak indeksujesz obiekt. Wydaje się to bardziej fundamentalne, __getslice__()więc zmieniłem powyższe.

steveha
źródło
2
@stantonk, dziękuję, że tak powiedziałeś, ale myślę, że już przyjąłem odpowiedź, kiedy to napisałem i tak naprawdę nie oczekuję, że zaakceptowana odpowiedź ulegnie zmianie.
steveha
@steveha: sreprto bardzo interesujący pomysł. Ale ja mam inne zdanie niż ty, czy to wymaga specjalnego przypadku str. Tak, strjest zdecydowanie najbardziej oczywistą i powszechną iteracją, która spowodowałaby nieskończoną rekurencję srepr. Ale mogę łatwo wyobrazić sobie iteratory zdefiniowane przez użytkownika, które zachowują się w ten sam sposób (z lub bez uzasadnionego powodu). Zamiast specjalnego przypadku str, powinniśmy przyznać, że takie podejście może spotkać się z nieskończoną rekurencją i zgodzić się na jakiś sposób radzenia sobie z tym. Zamieszczę moją sugestię w odpowiedzi.
maks.
1
Myślę, że to zdecydowanie właściwa ścieżka. Jednak, aby poradzić sobie ze specjalnym przypadkiem (sznurka w tym scenariuszu), myślę, że lepiej jest zadać pytanie „jak człowiek odróżniłby różnicę?” Rozważmy na przykład argument funkcji, który może być listą adresów e-mail lub pojedynczym adresem e-mail (pamiętając, że ciąg znaków jest po prostu listą znaków). Podaj tę zmienną człowiekowi. Jak można powiedzieć, co to jest? Najłatwiej mogę wymyślić, ile znaków jest w każdej pozycji na liście. Jeśli jest większy niż 1, argument z pewnością nie może być listą znaków.
Josh
1
Pomyślałem o tym trochę i przedyskutowałem to z kilkoma innymi osobami i myślę, że wszystko srepr()jest w porządku. Potrzebujemy funkcji rekurencyjnej do obsługi rzeczy takich jak lista zagnieżdżona na innej liście; ale w przypadku łańcuchów wolelibyśmy je wydrukować jako "foo"niż jako <'f', 'o', 'o'>. Dlatego wyraźne sprawdzenie łańcucha ma tutaj sens. Poza tym naprawdę nie ma innych przykładów typów danych, w których iteracja zawsze zwraca iterowalność, a rekurencja zawsze powoduje przepełnienie stosu, więc nie potrzebujemy specjalnej właściwości do testowania tego („Praktyczność pokonuje czystość”).
steveha
1
To nie działa w Pythonie 3, ponieważ ciągi znaków mają __iter__()metodę w Pythonie 3, ale nie w Pythonie 2. Brakuje nawiasów is_sequence(), powinien on brzmieć: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.
Mani Shankar Venkankatachalam
źródło
11
Tyle że nie używa on Pythonowego idiomu pisania kaczego, jak zauważyli inni komentatorzy (chociaż odpowiada bezpośrednio i czysto na pytanie).
Soren Bjornstad,
7
Ta odpowiedź jest mniej akceptowalna niż inne, ponieważ nie pozwala na pisanie kaczką, a także kończy się niepowodzeniem w prostym przypadku podklasy (typowym przykładem jest klasa namedtuple).
Philippe Gauthier
11
„Nie zezwalanie na pisanie kaczek” nie czyni odpowiedzi mniej akceptowalną, zwłaszcza biorąc pod uwagę, że odpowiedź ta faktycznie odpowiada na pytanie.
Petri
4
Poparłem tę odpowiedź, ale if isinstance( H, (list, tuple) ): ...jest ona krótsza i jaśniejsza.
shahar_m,
2
Alternatywna składnia:if type(H) in [list, tuple]:
Štefan Schindler,
77

W przypadku 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")

Zmieniono w wersji 3.3: Przeniesione kolekcje Abstrakcyjne klasy podstawowe do modułu collections.abc. Dla kompatybilności wstecznej będą one również widoczne w tym module aż do wersji 3.8, gdzie przestanie działać.

W przypadku 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
źródło
5
Łał! Działa to naprawdę ładnie i jest o wiele bardziej zwięzłe niż jakakolwiek inna poprawna odpowiedź. Nie miałem pojęcia, że ​​wbudowane typy dziedziczą, collections.Sequenceale przetestowałem to i widzę, że tak. Tak też jest xrange. Co więcej, ten test wyklucza dict, który ma zarówno __getitem__i __iter__.
Neil Mayhew
Wiesz, dlaczego wynik inspect.getmro(list)nie obejmuje Sequence? Jak mamy dowiedzieć się, co możemy zrobić, isinstancegdy getmronie wszystko pokazuje?
Steve Jorgensen
@SteveJorgensen Method Resolution Order określa ścieżkę wyszukiwania klas używaną przez Python do wyszukiwania właściwej metody do użycia w klasach. Sequencejest klasą abstrakcyjną.
suzanshakya
3
W Python3 możesz zastąpić isinstance (obj, łańcuch bazowy) isinstance (obj, str) i to powinno działać.
Adrian Keister
2
w Pythonie 3 potrzebujesz, a nie isinstance (obj, bytes) ... jeśli chcesz listę rzeczy, a nie tylko do wyliczenia bajtów ...
Erik Aronesty
35

Python o smaku PHP:

def is_array(var):
    return isinstance(var, (list, tuple))
Cesar
źródło
6
Python jest językiem kaczym, więc naprawdę powinieneś sprawdzić, czy var ma atrybut __getitem__. Również nazwa wprowadza w błąd, ponieważ istnieje również moduł tablicy. A var może być również numpy.ndarray lub dowolnym innym typem, który ma __getitem__. Zobacz stackoverflow.com/a/1835259/470560 dla poprawnej odpowiedzi.
peterhil
9
W @peterhil strrównież __getitem__Twój czek nie wykluczastr
erikbwork
9
Podobnie dykt. Sprawdzanie, czy __getitem__jest to zła rada tutaj.
Petri
10

Ogólnie rzecz biorąc, fakt, że funkcja, która iteruje obiekt, działa zarówno na ciągach, jak i krotkach i listach, jest bardziej cechą niż błędem. Na pewno możesz użyć isinstanceklawisza lub klawisza, aby sprawdzić argument, ale dlaczego?

To brzmi jak pytanie retoryczne, ale tak nie jest. Odpowiedź na „dlaczego powinienem sprawdzić typ argumentu?” prawdopodobnie zaproponuje rozwiązanie prawdziwego problemu, a nie postrzeganego problemu. Dlaczego błąd jest przekazywany do funkcji? Ponadto: jeśli jest to błąd, gdy ciąg znaków jest przekazywany do tej funkcji, to czy jest to również błąd, jeśli przekazywana jest do niego inna iterowalna lista / krotka? Dlaczego lub dlaczego nie?

Myślę, że najczęstszą odpowiedzią na to pytanie może być to, że programiści, którzy piszą, f("abc")oczekują, że funkcja będzie zachowywać się tak, jak gdyby napisali f(["abc"]). Prawdopodobnie istnieją sytuacje, w których bardziej sensowne jest chronienie programistów przed sobą, niż wspieranie przypadku iteracji znaków w ciągu. Ale najpierw długo się nad tym zastanowię.

Robert Rossney
źródło
16
„Najpierw jednak długo się nad tym zastanowię”. Nie zrobiłbym tego. Jeśli funkcja ma być funkcją list-y, to tak, powinna traktować je tak samo (tj. Biorąc pod uwagę listę, wypluj ją do tyłu, takie rzeczy). Jeśli jednak jest to funkcja, w której jednym z argumentów może być ciąg lub lista ciągów (co jest dość powszechną potrzebą), to zmuszanie programisty używającego tej funkcji do zawsze wprowadzania parametrów w tablicy wydaje się nieco dużo . Pomyśl także o tym, jak poradzisz sobie, powiedzmy, z danymi wejściowymi JSON. Na pewno chcesz traktować listę obiektów innych niż ciąg znaków.
Jordan Reiter
8

Wypróbuj to dla czytelności i najlepszych praktyk:

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

Mam nadzieję, że to pomoże.

Om Prakash
źródło
Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Juha Untinen
1
W Pythonie 3 jest to: from typing import List-> isinstance([1, 2, 3], List= Truei isinstance("asd", List)= False
Juha Untinen
5

strObiekt nie ma __iter__atrybutu

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

abyś mógł sprawdzić

assert hasattr(x, '__iter__')

i to również podniesie ładność AssertionErrordla każdego innego obiektu, który nie będzie iterowalny.

Edytować: Jak Tim wspomina w komentarzach, będzie to działać tylko w Pythonie 2.x, a nie 3.x

Moe
źródło
8
Ostrożnie: W Pythonie 3 hasattr('','__iter__')powraca True. Ma to oczywiście sens, ponieważ można iterować po łańcuchu.
Tim Pietzcker,
1
Naprawdę? Nie wiedziałem tego Zawsze myślałem, że to eleganckie rozwiązanie problemu, no cóż.
Moe
1
Ten test nie działał na pyodbc.Row. Nie ma iteru __ (), ale mniej więcej zachowuje się jak lista (definiuje nawet „__setitem ”). Możesz iterować jego elementy w porządku. Funkcja len () działa i możesz indeksować jej elementy. Usiłuję znaleźć odpowiednią kombinację, która przechwytuje wszystkie typy list, ale wyklucza ciągi. Myślę, że zadowolę się sprawdzeniem „ getitem ” i „ len ”, jednoznacznie wykluczając łańcuch podstawowy.
haridsv
5

To nie ma na celu bezpośredniej odpowiedzi na PO, ale chciałem podzielić się kilkoma powiązanymi pomysłami.

Byłem bardzo zainteresowany powyższą odpowiedzią @steveha, która zdawała się dawać przykład, w którym pisanie kaczek wydaje się łamać. Jednak przy drugim przemyśleniu jego przykład sugeruje, że pisanie kaczek jest trudne do dostosowania, ale nie sugeruje, że strzasługuje na specjalne podejście.

W końcu inny niż strtyp (np. Typ zdefiniowany przez użytkownika, który utrzymuje pewne skomplikowane struktury rekurencyjne) może spowodować, że sreprfunkcja @steveha spowoduje nieskończoną rekurencję. Chociaż jest to raczej mało prawdopodobne, nie możemy zignorować tej możliwości. Dlatego zamiast specjalnej obudowy strw srepr, powinniśmy wyjaśnić, co chcemy sreprzrobić, gdy nieskończonej wyniki rekursji.

Może się wydawać, że jednym rozsądnym podejściem jest po prostu przerwanie rekurencji w sreprdanym momencie list(arg) == [arg]. To całkowicie rozwiązałoby problem strbez żadnegoisinstance .

Jednak naprawdę skomplikowana struktura rekurencyjna może powodować nieskończoną pętlę, w której list(arg) == [arg]nigdy się nie zdarza. Dlatego chociaż powyższa kontrola jest przydatna, nie jest wystarczająca. Potrzebujemy czegoś w rodzaju twardego ograniczenia głębokości rekurencji.

Chodzi mi o to, że jeśli planujesz obsługiwać dowolne typy argumentów, obsługa strza pomocą pisania kaczego jest o wiele łatwiejsza niż obsługa bardziej ogólnych typów, które możesz (teoretycznie) napotkać. Jeśli więc czujesz potrzebę wykluczenia strinstancji, powinieneś zamiast tego zażądać, aby argument był instancją jednego z niewielu typów, które wyraźnie określiłeś.

max
źródło
1
Hmm, podoba mi się twój sposób myślenia. Myślę, że nie można argumentować, że mój kod jest praktyczny: istnieje dokładnie jeden wspólny przypadek str, który obsługuje kod specjalnego przypadku. Ale może powinna istnieć nowa standardowa właściwość, którą kod może sprawdzać, .__atomic__powiedzmy, która sygnalizuje, że czegoś nie da się dalej zepsuć. Prawdopodobnie jest już za późno, aby dodać kolejną wbudowaną funkcję atomic()do Pythona, ale może możemy to dodać from collections import atomiclub coś takiego.
steveha
5

W funkcji tensorflow znajduję taką funkcję o nazwie 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))

I zweryfikowałem, że spełnia twoje potrzeby.

Lerner Zhang
źródło
2

Robię to w moich testach.

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

Nieprzetestowane na generatorach, myślę, że pozostaniesz przy następnej „wydajności”, jeśli zostaniesz przekazany do generatora, co może zepsuć wszystko w dół rzeki. Ale z drugiej strony jest to „niestosowne”

FlipMcF
źródło
2

Co powiesz na „pisanie kaczką”?

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

lub

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

odpowiednio. Pozwala to uniknąć isinstance/hasattr introspekcji.

Możesz także sprawdzić na odwrót:

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

Wszystkie warianty tak naprawdę nie zmieniają zawartości zmiennej, ale sugerują zmianę przypisania. Nie jestem pewien, czy w pewnych okolicznościach może to być niepożądane.

Co ciekawe, przy przypisaniu „na miejscu” +=nie TypeErrorpojawi się żadna lista, jeśli lstjest to lista (a nie krotka ). Dlatego zadanie jest wykonywane w ten sposób. Może ktoś może wyjaśnić, dlaczego tak jest.

utobi
źródło
1

najprostszy sposób ... używając anyiisinstance

>>> 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
źródło
1

Inna wersja pisma kaczego, która pomaga odróżnić obiekty łańcuchowe od innych obiektów podobnych do sekwencji.

Reprezentacja ciągu obiektów podobnych do łańcucha jest samym łańcuchem, dzięki czemu można sprawdzić, czy otrzymano od strkonstruktora równy obiekt :

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

Powinno to działać dla wszystkich obiektów zgodnych z stri dla wszystkich rodzajów obiektów iterowalnych.

stevepastelan
źródło
0

Python 3 ma to:

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

Aby sprawdzić zarówno Listy, jak i Tuple, byłoby to:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
Juha Untinen
źródło
0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
ersh
źródło
2
czy to dobry sposób na zrobienie tego?
ersh 17.04.19
1
Witamy w SO. Pomocne byłoby wyjaśnienie, dlaczego ten kod odpowiada na pytanie.
Nick
Tak, oczywiście, używam metod podobnych do tego, ponieważ potok jest traktowany jako mniej więcej tak, że twierdzisz, że typ musi być listą lub krotką typu wyświetlającą niestandardowy błąd komunikatu w celu obsługi błędu. Wydaje mi się, że to odpowiada na pytanie, ale byłem ciekawy, czy jest to skuteczny sposób na zrobienie tego, ponieważ wciąż staram się opanować pisanie najbardziej zoptymalizowanego kodu. Nie jestem jednak pewien, czy w tym kodzie brakuje czegoś, co może zachowywać się jak listy / krotki, ale nie są podklasami żadnego z nich, ponieważ sposób, w jaki przyjęta odpowiedź rozwiązuje tę możliwość. Dzięki!
ersh
-1

Po prostu to zrób

if type(lst) in (list, tuple):
    # Do stuff
ATOzTOA
źródło
5
isinstance (pierwszy, (lista, krotka))
Davi Lima
@DaviLima OK, to inny sposób. Ale typ () jest zalecany dla typów wbudowanych i isinstance dla klas.
ATOzTOA,
-1

w python> 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
Jean Doux
źródło
2
W ostatnim czeku używasz typu str, a nie ciągu. Spróbuj, isinstance('my_string', collections.abc.Container)a zobaczysz, że wróci True. Wynika to z tego, że abc.Containerdostarcza __contains__metodę, a łańcuchy ją mają, oczywiście.
Georgy
-6

Zwykle to robię (jeśli naprawdę, naprawdę musiałem):

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
źródło
5
Prawie nigdy nie powinieneś tego robić. Co się stanie, jeśli będę some_varinstancją klasy, która jest podklasą list()? Twój kod nie będzie miał pojęcia, co z nim zrobić, nawet jeśli będzie działał idealnie pod kodem „zrób coś z listą”. I rzadko trzeba dbać o różnicę między listą a krotką. Przepraszam, -1.
steveha,
1
Nie musisz pisać type(tuple())- tuplezrobi. To samo dla listy. Ponadto zarówno stri unicoderozszerz basestring, który jest prawdziwym typem łańcucha, więc zamiast tego chcesz to sprawdzić.
traktuj swoje mody dobrze
@DrBloodmoney: Accidental downvote. Proszę (trywialnie) edytować swoją odpowiedź, aby umożliwić mi usunięcie opinii.
SabreWolfy
Dla mnie równość nie wydaje się sensownym porównaniem typów. Chciałbym przetestować tożsamości zamiast: type(i) is list. Ponadto, type(list())jest po prostu listsam… Wreszcie, nie działa to z gracją z podklasami. Jeśli iw rzeczywistości jest to OragedDict, lub jakiś nametuple, ten kod będzie traktowany jako ciąg znaków.
bukzor