Como verificar se um objeto é uma lista ou tupla (mas não string)?

443

Isto é o que eu normalmente faço para verificar se a entrada é um list/ tuple- mas não um str. Porque muitas vezes me deparei com bugs em que uma função passa um strobjeto por engano, e a função alvo for x in lstassume que lsté realmente um listou tuple.

assert isinstance(lst, (list, tuple))

Minha pergunta é: existe uma maneira melhor de conseguir isso?

Sridhar Ratnakumar
fonte
9
tipo (lst) é lista?
jackalope
1
não isinstance (key, six.string_types)
wyx

Respostas:

332

Somente no python 2 (não no python 3):

assert not isinstance(lst, basestring)

Na verdade, é o que você deseja, caso contrário, você perderá muitas coisas que funcionam como listas, mas não são subclasses de listou tuple.

Nick Craig-Wood
fonte
91
Sim, esta é a resposta correta. No Python 3, basestringse foi, e você apenas verifica isinstance(lst, str).
Steveha 02/12/2009
5
Você pode iterar muitas coisas, como listas, por exemplo set, expressões de gerador, iteradores. Existem coisas exóticas, coisas mmapmenos exóticas, arrayque agem como listas, e provavelmente muito mais que eu esqueci.
Nick Craig-Wood
50
É importante notar que este não garante que lsté iterável, enquanto o original fez (por exemplo, um int iria passar este cheque)
Peter Gibson
11
@ PeterGibson - Uma combinação dos dois fornecerá uma verificação válida e mais restritiva e garantirá que 1) lst é iterável, 2) lst não é uma string. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA
4
Bem, essa solução verifica apenas tipos derivados de cadeias, mas e números inteiros, duplos ou qualquer outro tipo não iterável?
Eneko Alonso
171

Lembre-se de que, em Python, queremos usar "digitação de pato". Portanto, qualquer coisa que atue como uma lista pode ser tratada como uma lista. Portanto, não verifique o tipo de uma lista, apenas veja se ela funciona como uma lista.

Mas as strings também funcionam como uma lista, e muitas vezes não é isso que queremos. Há momentos em que é até um problema! Portanto, verifique explicitamente se há uma string, mas use a digitação de pato.

Aqui está uma função que escrevi por diversão. É uma versão especial repr()que imprime qualquer sequência entre colchetes angulares ('<', '>').

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

Isso é limpo e elegante, em geral. Mas o que esse isinstance()cheque está fazendo lá? Isso é meio que um hack. Mas é essencial.

Essa função se chama recursivamente em qualquer coisa que atue como uma lista. Se não tratássemos a string especialmente, ela seria tratada como uma lista e dividiria um caractere de cada vez. Mas então a chamada recursiva tentaria tratar cada caractere como uma lista - e funcionaria! Mesmo uma cadeia de um caractere funciona como uma lista! A função continuaria se chamando recursivamente até o estouro da pilha.

Funções como esta, que dependem de cada chamada recursiva, dividindo o trabalho a ser realizado, têm cadeias de casos especiais - porque você não pode dividir uma cadeia abaixo do nível de uma cadeia de um caractere e até uma -caracter string atua como uma lista.

Nota: a try/ excepté a maneira mais limpa de expressar nossas intenções. Mas se esse código for de alguma forma crítico em termos de tempo, podemos substituí-lo por algum tipo de teste para ver se argé uma sequência. Em vez de testar o tipo, provavelmente devemos testar comportamentos. Se ele tem um .strip()método, é uma string, então não considere uma sequência; caso contrário, se for indexável ou iterável, é uma sequência:

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: Eu escrevi originalmente o acima com uma verificação, __getslice__()mas notei que na collectionsdocumentação do módulo, o método interessante é __getitem__(); isso faz sentido, é assim que você indexa um objeto. Isso parece mais fundamental do que __getslice__()eu mudei o acima.

steveha
fonte
2
@stantonk, obrigado por dizer isso, mas acho que já havia uma resposta aceita quando escrevi isso e realmente não espero que a resposta aceita seja alterada.
Steveha 25/05
@ steveha: srepré uma ideia muito interessante. Mas tenho uma opinião diferente da sua sobre se é necessário um caso especial str. Sim, stré de longe a iterável mais óbvia e comum que causaria uma recursão infinita no srepr. Mas posso facilmente imaginar iteráveis ​​definidos pelo usuário que se comportam da mesma maneira (com ou sem um bom motivo). Em vez de um caso especial str, devemos admitir que essa abordagem pode se deparar com uma recursão infinita e concordar com alguma maneira de lidar com ela. Vou postar minha sugestão em uma resposta.
Max
1
Eu acho que esse é definitivamente o caminho certo. No entanto, para lidar com o caso especial (de string neste cenário), acho que é melhor fazer a pergunta "como um humano diria a diferença?" Por exemplo, considere um argumento de função que pode ser uma lista de endereços de email ou um único endereço de email (tendo em mente que uma string é simplesmente uma lista de caracteres). Dê essa variável a um humano. Como poderia dizer qual é? A maneira mais fácil de pensar é ver quantos caracteres existem em cada item da lista. Se for maior que 1, o argumento certamente não pode ser uma lista de caracteres.
13133 Josh
1
Eu pensei um pouco sobre isso e discuti com algumas outras pessoas, e acho que srepr()está bem como está. Precisamos de uma função recursiva para lidar com coisas como uma lista aninhada dentro de outra lista; mas para strings, preferimos imprimi-las do "foo"que como <'f', 'o', 'o'>. Portanto, uma verificação explícita de uma sequência faz sentido aqui. Além disso, realmente não existem outros exemplos de tipos de dados nos quais a iteração sempre retorna uma iterável e a recursão sempre causa excesso de pilha; portanto, não precisamos de uma propriedade especial para testar isso ("A praticidade supera a pureza").
Steveha 21/03
1
Isso não funciona em Python 3, porque cordas têm um __iter__()método em Python 3, mas não em Python 2. Você está parênteses em falta is_sequence(), deve ler-se: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
fonte
11
Exceto que ele não usa o idioma Python de digitação de pato, como outros comentaristas apontaram (embora ele responda à pergunta de maneira direta e limpa).
Soren Bjornstad 18/09/2015
7
Essa resposta é menos aceitável do que outras, porque não permite a digitação de patos e também falha no caso simples de subclassificação (um exemplo típico é a classe nomeada dupla).
Philippe Gauthier
11
"Não permitir a digitação de patos" não torna a resposta menos aceitável, especialmente porque essa resposta realmente responde à pergunta.
Petri
4
Votou esta resposta, mas if isinstance( H, (list, tuple) ): ...é mais curta e mais clara.
shahar_m
2
Sintaxe alternativa:if type(H) in [list, tuple]:
Štefan Schindler 08/08
77

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

Alterado na versão 3.3: Movido as classes base abstratas de coleções para o módulo collections.abc. Para compatibilidade com versões anteriores, eles continuarão visíveis neste módulo até a versão 3.8, onde parará de funcionar.

Para 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
fonte
5
Uau! Isso funciona muito bem e é muito sucinto do que qualquer outra resposta correta. Eu não tinha idéia de que os tipos internos herdam, collections.Sequencemas eu testei e vejo que eles fazem. O mesmo acontece xrange. Ainda melhor, este teste exclui dict, que possui ambos __getitem__e __iter__.
Neil Mayhew
Alguma idéia de por que o resultado de inspect.getmro(list)não inclui Sequence? Como devemos descobrir o que podemos fazer isinstancequando getmronão mostra tudo?
Steve Jorgensen
A ordem de resolução do método @SteveJorgensen define o caminho de pesquisa de classe usado pelo Python para pesquisar o método correto a ser usado nas classes. Sequenceé uma classe abstrata.
suzanshakya
3
No Python3, você pode substituir isinstance (obj, basestring) por isinstance (obj, str), e isso deve funcionar.
Adrian Keister
2
em Python 3 você precisa e não isinstance (obj, bytes) ... se você quiser uma lista de coisas, e não apenas para enumerar bytes ...
Erik Aronesty
35

Python com sabor PHP:

def is_array(var):
    return isinstance(var, (list, tuple))
Cesar
fonte
6
Python é uma linguagem do tipo pato, então você realmente deve verificar se var tem atributo __getitem__. Além disso, o nome é enganoso, pois também há módulo de matriz. E o var também pode ser um numpy.ndarray ou qualquer outro tipo que possua __getitem__. Consulte stackoverflow.com/a/1835259/470560 para obter a resposta correta.
Peterhil 26/08/12
9
@peterhil strtambém tem __getitem__, portanto, o seu cheque não excluistr
erikbwork
9
O mesmo acontece com um ditado. Verificar __getitem__aqui é um mau conselho.
Petri
10

De um modo geral, o fato de uma função que itera sobre um objeto funcionar tanto em strings quanto em tuplas e listas é mais um recurso que um bug. Você certamente pode usar isinstanceou digitar para verificar um argumento, mas por que deveria?

Isso soa como uma pergunta retórica, mas não é. A resposta para "por que devo verificar o tipo de argumento?" provavelmente vai sugerir uma solução para o problema real, não para o problema percebido. Por que é um erro quando uma string é passada para a função? Além disso: se é um bug quando uma string é passada para essa função, também é um bug se alguma outra iterável não listada / tupla for passada para ela? Por que ou por que não?

Acho que a resposta mais comum para a pergunta provavelmente é que os desenvolvedores que escrevem f("abc")esperam que a função se comporte como se tivessem escrito f(["abc"]). Provavelmente, há circunstâncias em que faz mais sentido proteger os desenvolvedores de si mesmos do que apoiar o caso de uso de iteração entre os caracteres em uma sequência. Mas eu pensaria muito nisso primeiro.

Robert Rossney
fonte
16
"Mas eu pensaria muito sobre isso primeiro." Eu não faria. Se a função deveria ser uma função da lista y, sim, ela deveria ser tratada da mesma forma (ou seja, dada uma lista, cuspa-a para trás, coisas assim). No entanto, se é uma função em que um dos argumentos pode ser uma string ou uma lista de strings (o que é uma necessidade bastante comum), forçar o desenvolvedor que usa essa função a sempre inserir seu parâmetro dentro de uma matriz parece um pouco demais . Além disso, pense em como você manipularia, digamos, a entrada JSON. Você definitivamente gostaria de tratar uma lista de objetos diferente de uma string.
Jordan Reiter
8

Tente isso para facilitar a leitura e as práticas recomendadas:

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

Espero que ajude.

Om Prakash
fonte
Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Juha Untinen
1
No Python 3, é: from typing import List-> isinstance([1, 2, 3], List= Truee isinstance("asd", List)= False
Juha Untinen
5

O strobjeto não tem um __iter__atributo

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

para que você possa fazer uma verificação

assert hasattr(x, '__iter__')

e isso também aumentará a importância AssertionErrorde qualquer outro objeto não iterável.

Edit: Como Tim menciona nos comentários, isso funcionará apenas no python 2.x, não no 3.x

Moe
fonte
8
Cuidado: em Python 3 hasattr('','__iter__')retorna True. E, claro, isso faz sentido, pois você pode iterar sobre uma string.
Tim Pietzcker 02/12/2009
1
Mesmo? Eu não sabia disso. Eu sempre pensei que essa era uma solução elegante para o problema, tudo bem.
Moe
1
Este teste não funcionou no pyodbc.Row. Não possui iter __ (), mas se comporta mais ou menos como uma lista (até define "__setitem "). Você pode iterar seus elementos muito bem. A função len () funciona e você pode indexar seus elementos. Estou lutando para encontrar a combinação certa que captura todos os tipos de lista, mas exclui seqüências de caracteres. Acho que vou me contentar com uma verificação de " getitem " e " len ", excluindo explicitamente a base de dados.
haridsv
5

Isso não pretende responder diretamente ao OP, mas eu queria compartilhar algumas idéias relacionadas.

Eu estava muito interessado na resposta @steveha acima, que parecia dar um exemplo em que a digitação do pato parece quebrar. Pensando bem, no entanto, seu exemplo sugere que a digitação com patos é difícil de se adaptar, mas não sugere que strmereça um tratamento especial.

Afinal, um não- strtipo (por exemplo, um tipo definido pelo usuário que mantém algumas estruturas recursivas complicadas) pode fazer com que a sreprfunção @steveha cause uma recursão infinita. Embora isso seja bastante improvável, não podemos ignorar essa possibilidade. Portanto, ao invés de especial-invólucro strem srepr, devemos esclarecer o que queremos sreprfazer quando um infinitas resultados recursão.

Pode parecer que uma abordagem razoável seja simplesmente interromper a recursão no sreprmomento list(arg) == [arg]. De fato, isso resolveria completamente o problema com str, sem nenhum isinstance.

No entanto, uma estrutura recursiva realmente complicada pode causar um loop infinito onde list(arg) == [arg]nunca acontece. Portanto, embora a verificação acima seja útil, não é suficiente. Precisamos de algo como um limite rígido na profundidade da recursão.

Meu argumento é que, se você planeja lidar com tipos de argumentos arbitrários, o manuseio strpor meio da digitação com patos é muito, muito mais fácil do que com os tipos mais gerais que você pode (teoricamente) encontrar. Portanto, se você sentir a necessidade de excluir strinstâncias, deve exigir que o argumento seja uma instância de um dos poucos tipos especificados explicitamente.

max
fonte
1
Hmm, eu gosto do jeito que você pensa. Eu acho que você não pode argumentar que meu código é prático: existe exatamente um caso comum str, que o código de caso especial trata. Mas talvez deva haver uma nova propriedade padrão que o código possa inspecionar, .__atomic__digamos, que sinalize que algo não pode mais ser quebrado. Provavelmente é tarde demais para adicionar outra função integrada atomic()ao Python, mas talvez possamos adicionar from collections import atomicalgo assim.
Steveha 29/10/12
5

Eu encontro uma função chamada is_sequence no 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 verifiquei que atende às suas necessidades.

Lerner Zhang
fonte
2

Eu faço isso em minhas caixas de teste.

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

Não testado em geradores, acho que você fica no próximo 'rendimento' se for passado em um gerador, o que pode estragar tudo a jusante. Mas, novamente, este é um 'unittest'

FlipMcF
fonte
2

No modo "digitação de pato", que tal

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

ou

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

respectivamente. Isso evita o material isinstance/ hasattrintrospecção.

Você também pode verificar o contrário:

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

Todas as variantes, na verdade, não alteram o conteúdo da variável, mas implicam em uma reatribuição. Não tenho certeza se isso pode ser indesejável em algumas circunstâncias.

Curiosamente, com a atribuição "no local", +=nenhum TypeErrorseria aumentado em qualquer caso se lstfor uma lista (não uma tupla ). É por isso que a tarefa é feita dessa maneira. Talvez alguém possa esclarecer o porquê disso.

utobi
fonte
1

maneira mais simples ... 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
abarik
fonte
1

Outra versão da digitação de pato para ajudar a distinguir objetos do tipo seqüência de caracteres de outros objetos do tipo seqüência.

A representação de string de objetos do tipo string é a própria string, portanto você pode verificar se obtém um objeto igual do strconstrutor:

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

Isso deve funcionar para todos os objetos compatíveis com stre para todos os tipos de objetos iteráveis.

stevepastelan
fonte
0

O Python 3 tem isso:

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

Portanto, para verificar listas e tuplas, seria:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
Juha Untinen
fonte
0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
ersh
fonte
2
esta é uma maneira correta de fazer isso?
Ersh
1
Bem-vindo ao SO. Uma explicação de por que esse código responde à pergunta seria útil.
Nick
Sim, é claro, eu uso métodos semelhantes a esse, pois o canal é tratado como um ou mais, e você está afirmando que o tipo deve ser uma lista ou tupla do tipo, gerando um erro de mensagem personalizado para o tratamento de erros. Acredito que ele responda à pergunta, mas fiquei curioso como se fosse uma maneira eficaz de fazer isso, pois ainda estou tentando entender o código mais otimizado. No entanto, não tenho certeza se esse código perde coisas que podem funcionar como listas / tuplas, mas também não são subclasses, como a resposta aceita aborda essa possibilidade. Obrigado!
Ersh 18/04
-1

Apenas faça isso

if type(lst) in (list, tuple):
    # Do stuff
ATOzTOA
fonte
5
isinstance (lst, (list, tuple))
Davi Lima
@ DaviLima OK, essa é outra maneira. Mas type () é recomendado para tipos incorporados e é uma instância para Classes.
ATOzTOA
-1

em 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
fonte
2
Na última verificação, você usa um tipo str, não uma string. Tente isinstance('my_string', collections.abc.Container)e você verá que ele retornará True. Isso ocorre porque abc.Containerfornece o __contains__método e as seqüências de caracteres, é claro.
Georgy
-6

Costumo fazer isso (se realmente, realmente):

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
fonte
5
Você quase nunca deveria fazer isso. O que acontece se eu some_varfor uma instância de uma classe que é uma subclasse de list()? Seu código não tem idéia do que fazer com ele, mesmo que funcione perfeitamente no código "faça algo com uma lista". E você raramente precisa se preocupar com a diferença entre uma lista e uma tupla. Desculpe -1.
Steveha 02/12/2009
1
Não há necessidade de escrever type(tuple())- tuplevai fazer. O mesmo para a lista. Além disso, both stre unicodeextend basestring, que é o tipo de string real, então você deseja verificar isso.
trate bem seus mods
@DrBloodmoney: votação acidental. Edite (trivialmente) sua resposta para permitir que eu remova o voto negativo.
SabreWolfy
A igualdade não me parece uma comparação significativa para os tipos. Eu testar a identidade em vez disso: type(i) is list. Além disso, type(list())é apenas listele mesmo ... Finalmente, isso não funciona normalmente com subclasses. Se ié de fato e OrderedDict, ou algum tipo de nome nomeado, este código tratará como uma sequência.
bukzor