Come verificare se un oggetto è un elenco o una tupla (ma non una stringa)?


444

Questo è ciò che faccio normalmente per accertare che l'input sia un list/ tuple- ma non un str. Perché molte volte mi sono imbattuto in bug in cui una funzione passa strper errore un oggetto e la funzione target for x in lstpresuppone che lstsia effettivamente un listo tuple.

assert isinstance(lst, (list, tuple))

La mia domanda è: esiste un modo migliore per raggiungere questo obiettivo?


9
tipo (primo) è la lista?
Jackalope,

1
not isinstance (key, six.string_types)
wyx,

Risposte:


332

Solo in python 2 (non in python 3):

assert not isinstance(lst, basestring)

In realtà è quello che vuoi, altrimenti ti perderai molte cose che si comportano come elenchi, ma non sono sottoclassi di listo tuple.


91
Sì, questa è la risposta corretta. In Python 3, non basestringc'è più, e basta cercare isinstance(lst, str).
Steveha,

5
Ci sono molte cose che puoi iterare come elenchi, ad esempio setespressioni di generatori, iteratori. Ci sono cose esotiche come mmap, cose meno esotiche come quelle arrayche si comportano più o meno come liste, e probabilmente molte altre che ho dimenticato.
Nick Craig-Wood,

50
Vale la pena notare che questo non garantisce che lstsia iterabile, mentre l'originale ha fatto (ad esempio un int avrebbe passato questo controllo)
Peter Gibson,

11
@PeterGibson - Una combinazione dei due fornirà un controllo valido e più restrittivo e garantirà 1) il primo è iterabile, 2) il primo non è una stringa. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA,

4
Bene, questa soluzione controlla solo i tipi derivati ​​da stringa, ma per quanto riguarda numeri interi, doppi o qualsiasi altro tipo non iterabile?
Eneko Alonso,

171

Ricorda che in Python vogliamo usare la "tipizzazione anatra". Quindi, tutto ciò che si comporta come un elenco può essere trattato come un elenco. Quindi, non controllare il tipo di un elenco, vedi solo se si comporta come un elenco.

Ma anche le stringhe si comportano come un elenco e spesso non è quello che vogliamo. Ci sono momenti in cui è persino un problema! Quindi, controlla esplicitamente una stringa, ma poi usa la tipizzazione duck.

Ecco una funzione che ho scritto per divertimento. È una versione speciale repr()che stampa qualsiasi sequenza tra parentesi angolari ('<', '>').

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

Questo è pulito ed elegante, nel complesso. Ma che isinstance()ci fa quel controllo? È una specie di hack. Ma è essenziale.

Questa funzione si chiama ricorsivamente su tutto ciò che si comporta come un elenco. Se non gestissimo la stringa in modo speciale, verrebbe trattata come una lista e divisa un carattere alla volta. Ma poi la chiamata ricorsiva proverebbe a trattare ogni personaggio come un elenco - e funzionerebbe! Anche una stringa di un carattere funziona come un elenco! La funzione continuerebbe a richiamare se stessa in modo ricorsivo fino allo overflow dello stack.

Funzioni come questa, che dipendono da ogni chiamata ricorsiva che interrompe il lavoro da svolgere, devono stringhe di casi speciali, poiché non è possibile suddividere una stringa al di sotto del livello di una stringa di un carattere e persino una -character stringa si comporta come un elenco.

Nota: il try/ exceptè il modo più pulito per esprimere le nostre intenzioni. Ma se questo codice fosse in qualche modo critico nel tempo, potremmo volerlo sostituire con una sorta di test per vedere se argè una sequenza. Invece di testare il tipo, dovremmo probabilmente testare i comportamenti. Se ha un .strip()metodo, è una stringa, quindi non considerarla una sequenza; altrimenti, se è indicizzabile o iterabile, è una sequenza:

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: originariamente ho scritto quanto sopra con un controllo per __getslice__()ma ho notato che nella collectionsdocumentazione del modulo, il metodo interessante è __getitem__(); questo ha senso, è così che indicizzi un oggetto. Sembra più fondamentale di __getslice__()così, ho cambiato quanto sopra.


2
@stantonk, grazie per averlo detto, ma penso che ci fosse già una risposta accettata quando l'ho scritta e non mi aspetto davvero che la risposta accettata venga modificata.
Steveha,

@steveha: sreprè un'idea molto interessante. Ma ho un'opinione diversa rispetto a te sull'opportunità di un caso speciale str. Sì, strè di gran lunga l'iterabile più ovvio e comune che causerebbe una ricorsione infinita srepr. Ma posso facilmente immaginare iterable definiti dall'utente che si comportano allo stesso modo (con o senza una buona ragione). Piuttosto che un caso speciale str, dovremmo ammettere che questo approccio può incorrere in una ricorsione infinita e concordare un modo per affrontarlo. Invierò il mio suggerimento in una risposta.
massimo

1
Penso che questa sia sicuramente la strada giusta. Tuttavia, per gestire il caso speciale (della stringa in questo scenario), penso che sia meglio porre la domanda "come farebbe un essere umano a dire la differenza?" Ad esempio, considera un argomento di funzione che può essere un elenco di indirizzi e-mail o un singolo indirizzo e-mail (tenendo presente che una stringa è semplicemente un elenco di caratteri). Dai questa variabile a un essere umano. Come ha potuto dire quale è? Il modo più semplice a cui riesco a pensare è vedere quanti personaggi ci sono in ogni elemento della lista. Se è maggiore di 1, l'argomento certamente non può essere un elenco di caratteri.
Josh,

1
Ci ho pensato un po 'e ne ho discusso con alcune altre persone, e penso che vada srepr()bene così com'è. Abbiamo bisogno di una funzione ricorsiva per gestire cose come un elenco nidificato all'interno di un altro elenco; ma per le stringhe preferiremmo stamparle piuttosto "foo"che come <'f', 'o', 'o'>. Quindi un controllo esplicito per una stringa ha senso qui. Inoltre, in realtà non ci sono altri esempi di tipi di dati in cui l'iterazione restituisce sempre un iterabile e la ricorsione causerà sempre un overflow dello stack, quindi non abbiamo bisogno di una proprietà speciale per verificarlo ("La praticità batte la purezza").
Steveha,

1
Questo non funziona in Python 3, perché le stringhe hanno un __iter__()metodo in Python 3, ma non in Python 2. Ti mancano le parentesi is_sequence(), si dovrebbe leggere: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
Tranne per il fatto che non usa il linguaggio Python della tipizzazione duck come hanno sottolineato altri commentatori (anche se risponde alla domanda in modo diretto e pulito).
Soren Bjornstad,

7
Questa risposta è meno accettabile di altre perché non consente la tipizzazione anatra e fallisce anche nel semplice caso della sottoclasse (un esempio tipico è la classe namedtuple).
Philippe Gauthier,

11
"Non consentire la digitazione di anatre" non rende la risposta meno accettabile, soprattutto perché questa risposta risponde effettivamente alla domanda.
Petri,

4
Ho votato a favore di questa risposta, ma if isinstance( H, (list, tuple) ): ...è più breve e più chiaro.
shahar_m,

2
Sintassi alternativa:if type(H) in [list, tuple]:
Štefan Schindler

77

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

Modificato nella versione 3.3: Classi di base astratte delle raccolte spostate nel modulo collections.abc. Per compatibilità con le versioni precedenti, continueranno a essere visibili anche in questo modulo fino alla versione 3.8, dove smetterà di funzionare.

Per 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
Wow! Funziona davvero bene ed è molto più conciso di qualsiasi altra risposta corretta. Non avevo idea che i tipi predefiniti ereditino, collections.Sequencema l'ho provato e vedo che lo fanno. Così fa xrange. Ancora meglio, questo test esclude dict, che ha entrambi __getitem__e __iter__.
Neil Mayhew,

Qualche idea sul perché il risultato di inspect.getmro(list)non includa Sequenceperò? Come dovremmo capire cosa possiamo fare isinstancequando getmronon mostra tutto?
Steve Jorgensen,

L'ordine di risoluzione del metodo @SteveJorgensen definisce il percorso di ricerca della classe utilizzato da Python per cercare il metodo corretto da utilizzare nelle classi. Sequenceè una classe astratta.
suzanshakya,

3
In Python3, puoi sostituire isinstance (obj, basestring) con isinstance (obj, str) e dovrebbe funzionare.
Adrian Keister,

2
in Python 3 hai bisogno e non isinstance (obj, byte) ... se vuoi un elenco di cose, e non solo per enumerare i byte ...
Erik Aronesty

35

Python con sapore PHP:

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

6
Python è un linguaggio tipizzato come anatra, quindi dovresti davvero controllare se var ha un attributo __getitem__. Inoltre il nome è fuorviante, in quanto esiste anche un modulo array. E var potrebbe anche essere un numpy.ndarray o qualsiasi altro tipo, che ha __getitem__. Vedere stackoverflow.com/a/1835259/470560 per la risposta corretta.
peterhil,

9
@peterhil strha __getitem__quindi anche il tuo assegno non escludestr
erikbwork il

9
Anche un dict. Controllare __getitem__è un cattivo consiglio qui.
Petri,

10

In generale, il fatto che una funzione che scorre su un oggetto funzioni sia su stringhe che su tuple ed elenchi è più una caratteristica che un bug. Certamente puoi usare isinstanceo anatra digitando per controllare un argomento, ma perché dovresti?

Sembra una domanda retorica, ma non lo è. La risposta a "perché dovrei controllare il tipo di argomento?" probabilmente suggerirà una soluzione al problema reale, non al problema percepito. Perché è un bug quando una stringa viene passata alla funzione? Inoltre: se è un bug quando una stringa viene passata a questa funzione, è anche un bug se qualche altro iterable non-list / tuple gli viene passato? Perché o perché no?

Penso che la risposta più comune alla domanda sia probabilmente che gli sviluppatori che scrivono f("abc")si aspettano che la funzione si comporti come se avessero scritto f(["abc"]). Ci sono probabilmente circostanze in cui ha più senso proteggere gli sviluppatori da se stessi che supportare il caso d'uso di iterare attraverso i caratteri in una stringa. Ma prima ci penserei a lungo.


16
"Ma prima ci penserei a lungo." Non lo farei. Se la funzione dovrebbe essere una funzione list-y, allora sì, dovrebbe trattarli allo stesso modo (cioè, dato un elenco, sputalo all'indietro, cose del genere). Tuttavia, se si tratta di una funzione in cui uno degli argomenti può essere una stringa o un elenco di stringhe (che è un'esigenza piuttosto comune), forzare lo sviluppatore a utilizzare quella funzione per immettere sempre il proprio parametro all'interno di un array sembra un po 'troppo . Inoltre, pensa a come gestiresti, per esempio, l'input JSON. Ti consigliamo di trattare un elenco di oggetti diversi da una stringa.
Jordan Reiter,

8

Prova questo per la leggibilità e le migliori pratiche:

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

Spero che sia d'aiuto.


Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Juha Untinen

1
In Python 3, è: from typing import List-> isinstance([1, 2, 3], List= True, e isinstance("asd", List)= False
Juha Untinen

5

L' stroggetto non ha un __iter__attributo

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

così puoi fare un controllo

assert hasattr(x, '__iter__')

e questo aumenterà anche un valore AssertionErrorper qualsiasi altro oggetto non iterabile.

Modifica: Come accenna Tim nei commenti, questo funzionerà solo in Python 2.x, non 3.x


8
Attenzione: in Python 3 hasattr('','__iter__')ritorna True. E ovviamente questo ha senso dal momento che puoi iterare su una stringa.
Tim Pietzcker,

1
Veramente? Non lo sapevo. Ho sempre pensato che fosse una soluzione elegante al problema, vabbè.
Moe,

1
Questo test non ha funzionato su pyodbc.Row. Non ha iter __ () ma si comporta più o meno come un elenco (definisce persino "__setitem "). Puoi iterare bene i suoi elementi. La funzione len () funziona e puoi indicizzare i suoi elementi. Faccio fatica a trovare la giusta combinazione che cattura tutti i tipi di elenco, ma esclude le stringhe. Penso che mi accontenterò di un controllo su " getitem " e " len " escludendo esplicitamente il basestring.
Haridsv,

5

Questo non ha lo scopo di rispondere direttamente al PO, ma volevo condividere alcune idee correlate.

Ero molto interessato alla risposta di @steveha sopra, che sembrava dare un esempio in cui la digitazione delle anatre sembra rompersi. A pensarci bene, tuttavia, il suo esempio suggerisce che la tipizzazione di anatre è difficile da conformare, ma non lo suggeriscestr meriti una gestione speciale.

Dopotutto, un non- strtipo (ad esempio, un tipo definito dall'utente che mantiene alcune strutture ricorsive complicate) può causare la sreprfunzione @steveha per provocare una ricorsione infinita. Anche se questo è certamente piuttosto improbabile, non possiamo ignorare questa possibilità. Pertanto, piuttosto che speciale involucro strin srepr, dobbiamo chiarire che cosa vogliamosrepr fare quando un infinito risultati ricorsione.

Può sembrare che un approccio ragionevole sia semplicemente quello di interrompere la ricorsione nel sreprmomento list(arg) == [arg]. Questo, in effetti, risolverebbe completamente il problema con str, senza nessunoisinstance .

Tuttavia, una struttura ricorsiva davvero complicata può causare un ciclo infinito dove list(arg) == [arg]non accade mai. Pertanto, sebbene il controllo sopra riportato sia utile, non è sufficiente. Abbiamo bisogno di qualcosa come un limite rigido alla profondità della ricorsione.

Il mio punto è che se si prevede di gestire tipi di argomenti arbitrari, la gestione strtramite la tipizzazione anatra è molto, molto più semplice rispetto alla gestione dei tipi più generali che si possono incontrare (teoricamente). Quindi, se senti la necessità di escludere le stristanze, dovresti invece richiedere che l'argomento sia un'istanza di uno dei pochi tipi specificati in modo esplicito.


1
Hmm, mi piace il modo in cui pensi. Penso che non si possa sostenere che il mio codice sia pratico: esiste esattamente un caso comune str, che il codice del caso speciale gestisce. Ma forse dovrebbe esserci una nuova proprietà standard che il codice può ispezionare, .__atomic__diciamo, che segnala che qualcosa non può essere ulteriormente scomposto. Probabilmente è troppo tardi per aggiungere un'altra funzione incorporata atomic()a Python, ma forse possiamo aggiungere from collections import atomicqualcosa.
Steveha,

5

Trovo una tale funzione chiamata is_sequence in 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))

E ho verificato che soddisfa le tue esigenze.


2

Lo faccio nei miei test.

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

Non testato sui generatori, penso che rimarrai al prossimo 'rendimento' se passato in un generatore, che potrebbe rovinare le cose a valle. Ma poi di nuovo, questo è un 'unittest'


2

In modo "tipizzazione anatra", che ne dici

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

o

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

rispettivamente. Questo evita il isinstance/hasattr cose introspezione.

Puoi anche controllare viceversa:

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

Tutte le varianti non cambiano effettivamente il contenuto della variabile, ma implicano una riassegnazione. Non sono sicuro che ciò possa essere indesiderabile in alcune circostanze.

È interessante notare che con l'assegnazione "sul posto" +=non TypeErrorverrebbe sollevato in ogni caso se si lsttratta di un elenco (non di una tupla ). Ecco perché il compito viene svolto in questo modo. Forse qualcuno può far luce sul perché.


1

modo più semplice ... usando anyeisinstance

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

Un'altra versione della tipizzazione anatra per aiutare a distinguere oggetti simili a stringhe da altri oggetti simili a sequenze.

La rappresentazione in stringa di oggetti simili a stringhe è la stringa stessa, quindi puoi verificare se ottieni un oggetto uguale dal strcostruttore:

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

Questo dovrebbe funzionare per tutti gli oggetti compatibili con stre per tutti i tipi di oggetti iterabili.


0

Python 3 ha questo:

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

Quindi, per controllare sia Elenchi che Tuple, sarebbe:

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
è un modo ok per farlo?
1919

1
Benvenuti in SO. Una spiegazione del perché questo codice risponde alla domanda sarebbe utile.
Nick,

Sì, certo, io uso metodi simili a questo poiché la pipe viene trattata come una o giù di lì e stai affermando che il tipo deve essere un elenco o tipo tupla che genera un errore di messaggio personalizzato per la gestione degli errori. Credo che risponda alla domanda, ma ero curioso come se fosse un modo efficace per farlo poiché sto ancora cercando di capire come scrivere il codice più ottimizzato. Non sono sicuro, tuttavia, se questo codice manca di cose che possono agire come elenchi / tuple ma che non sono sottoclassi di entrambi, in quanto il modo in cui la risposta accettata risponde a tale possibilità. Grazie!
Ersh

-1

Fallo e basta

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

5
isinstance (lst, (list, tuple))
Davi Lima,

@DaviLima OK, questo è un altro modo. Ma type () è raccomandato per i tipi integrati e isinstance per Classes.
ATOzTOA

-1

in 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

2
Nell'ultimo controllo si utilizza un tipo str, non una stringa. Prova isinstance('my_string', collections.abc.Container)e vedrai che tornerà True. Questo perché abc.Containerfornisce il __contains__metodo e le stringhe lo hanno, ovviamente.
Georgy

-6

Tendo a fare questo (se davvero, davvero dovevo):

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
Non dovresti quasi mai farlo. Cosa succede se sono some_varun'istanza di una classe che è una sottoclasse di list()? Il tuo codice non avrà idea di cosa farne, anche se funzionerà perfettamente con il codice "fai qualcosa con un elenco". E raramente devi preoccuparti della differenza tra un elenco e una tupla. Scusa, -1.
steveha,

1
Non c'è bisogno di scrivere type(tuple())- tuplelo farà. Lo stesso per l'elenco. Inoltre, sia stred unicodeestende basestring, che è il tipo di stringa reale, quindi è necessario verificarlo.
Tratta bene le tue mod il

@DrBloodmoney: downvote accidentale. Per favore (banalmente) modifica la tua risposta per consentirmi di rimuovere il downvote.
SabreWolfy,

L'uguaglianza non sembra un confronto significativo per i tipi, per me. Mi piacerebbe prova per l'identità invece: type(i) is list. Inoltre, type(list())è solo listse stesso ... Infine, questo non funziona con grazia con le sottoclassi. Se iè in realtà e OrderedDict, o un qualche tipo di namedtuple, questo codice tratterà come una stringa.
bukzor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.