¿Cómo verificar si un objeto es una lista o una tupla (pero no una cadena)?

443

Esto es lo que normalmente hago para determinar que la entrada es a list/ tuple- pero no a str. Porque muchas veces me topé con errores en los que una función pasa un strobjeto por error, y la función de destino for x in lstsupone que en lstrealidad es un listo tuple.

assert isinstance(lst, (list, tuple))

Mi pregunta es: ¿hay una mejor manera de lograr esto?

Sridhar Ratnakumar
fuente
99
tipo (lst) es la lista?
jackalope
1
not isinstance (key, six.string_types)
wyx

Respuestas:

332

Solo en Python 2 (no Python 3):

assert not isinstance(lst, basestring)

En realidad es lo que quieres, de lo contrario te perderás muchas cosas que actúan como listas, pero no son subclases de listo tuple.

Nick Craig-Wood
fuente
9191
Sí, esta es la respuesta correcta. En Python 3, basestringdesapareció, y solo lo verificas isinstance(lst, str).
steveha 02 de
55
Hay muchas cosas que puede iterar, como listas, por ejemplo set, expresiones generadoras, iteradores. Hay cosas exóticas, cosas mmapmenos exóticas arrayque actúan más o menos como listas, y probablemente muchas más que he olvidado.
Nick Craig-Wood, el
50
Vale la pena señalar que esto no garantiza que lstsea ​​iterable, mientras que el original sí (por ejemplo, un int pasaría esta verificación)
Peter Gibson
11
@PeterGibson: una combinación de los dos proporcionará una verificación válida y más restrictiva y garantizará que 1) lst sea iterable, 2) lst no sea una cadena. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA
44
Bueno, esta solución solo busca tipos derivados de cadenas, pero ¿qué pasa con los enteros, dobles o cualquier otro tipo no iterable?
Eneko Alonso el
171

Recuerde que en Python queremos usar "escribir pato". Entonces, cualquier cosa que actúe como una lista puede tratarse como una lista. Por lo tanto, no verifique el tipo de lista, solo vea si actúa como una lista.

Pero las cadenas también actúan como una lista, y a menudo eso no es lo que queremos. ¡Hay momentos en que incluso es un problema! Por lo tanto, verifique explícitamente una cadena, pero luego use la escritura de pato.

Aquí hay una función que escribí por diversión. Es una versión especial de repr()que imprime cualquier secuencia entre paréntesis 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

Esto es limpio y elegante, en general. ¿Pero qué hace ese isinstance()cheque allí? Eso es una especie de truco. Pero es esencial.

Esta función se llama recursivamente sobre cualquier cosa que actúe como una lista. Si no manejáramos la cadena especialmente, entonces sería tratada como una lista y dividiría un carácter a la vez. Pero entonces la llamada recursiva trataría de tratar a cada personaje como una lista, ¡y funcionaría! ¡Incluso una cadena de un carácter funciona como una lista! La función seguiría llamándose recursivamente hasta que se desborde la pila.

Las funciones como esta, que dependen de cada llamada recursiva que desglosa el trabajo a realizar, tienen que ser cadenas de casos especiales, porque no puede dividir una cadena por debajo del nivel de una cadena de un carácter, e incluso una La cadena de caracteres actúa como una lista.

Nota: la try/ exceptes la forma más limpia de expresar nuestras intenciones. Pero si este código fuera de alguna manera crítico en el tiempo, podríamos reemplazarlo con algún tipo de prueba para ver si arges una secuencia. En lugar de probar el tipo, probablemente deberíamos probar los comportamientos. Si tiene un .strip()método, es una cadena, así que no lo considere una secuencia; de lo contrario, si es indexable o iterable, es una secuencia:

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)

EDITAR: Originalmente escribí lo anterior con un cheque __getslice__()pero noté que en la collectionsdocumentación del módulo, el método interesante es __getitem__(); esto tiene sentido, así es como indizas un objeto. Eso parece más fundamental que __getslice__()eso, cambié lo anterior.

steveha
fuente
2
@stantonk, gracias por decirlo, pero creo que ya había una respuesta aceptada cuando escribí esto y realmente no espero que se cambie la respuesta aceptada.
steveha
@steveha: srepres una idea muy interesante. Pero tengo una opinión diferente a la tuya sobre si necesita un caso especial str. Sí, stres con mucho el iterativo más obvio y común que causaría una recursión infinita en srepr. Pero puedo imaginar fácilmente iterables definidos por el usuario que se comporten de la misma manera (con o sin una buena razón). En lugar de un caso especial str, debemos admitir que este enfoque puede encontrarse con una recursión infinita, y aceptar alguna forma de tratarlo. Publicaré mi sugerencia en una respuesta.
max
1
Creo que este es definitivamente el camino correcto. Sin embargo, para manejar el caso especial (de cadena en este escenario), creo que es mejor que nos hagamos la pregunta "¿cómo diría un humano la diferencia?" Por ejemplo, considere un argumento de función que puede ser una lista de direcciones de correo electrónico o una sola dirección de correo electrónico (teniendo en cuenta que una cadena es simplemente una lista de caracteres). Dar esta variable a un humano. ¿Cómo podría saber cuál es? La forma más fácil que se me ocurre es ver cuántos caracteres hay en cada elemento de la lista. Si es mayor que 1, el argumento ciertamente no puede ser una lista de caracteres.
Josh
1
He pensado un poco en esto y lo he discutido con algunas otras personas, y creo que srepr()está bien como está. Necesitamos una función recursiva para manejar cosas como una lista anidada dentro de otra lista; pero para las cadenas preferimos que se impriman como "foo"antes <'f', 'o', 'o'>. Entonces, una comprobación explícita de una cadena tiene sentido aquí. Además, realmente no hay otros ejemplos de tipos de datos en los que la iteración siempre devuelva un iterable y la recursión siempre cause un desbordamiento de la pila, por lo que no necesitamos una propiedad especial para probar esto ("La practicidad supera la pureza").
steveha
1
Esto no funciona en Python 3, porque las cadenas tienen un __iter__()método en Python 3, pero no en Python 2. Le faltan paréntesis is_sequence(), debería leer: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
fuente
11
Excepto que no usa el lenguaje de Python de escribir pato como lo han señalado otros comentaristas (aunque sí responde la pregunta directa y limpiamente).
Soren Bjornstad
77
Esta respuesta es menos aceptable que otras porque no permite escribir pato y también falla en el caso simple de la subclase (un ejemplo típico es la clase namedtuple).
Philippe Gauthier
11
"No permitir el tipeo de patos" no hace que la respuesta sea menos aceptable, especialmente dado que esta respuesta realmente responde a la pregunta.
Petri
44
He votado a favor de esta respuesta, pero if isinstance( H, (list, tuple) ): ...es más corta y clara.
shahar_m
2
Sintaxis alternativa:if type(H) in [list, tuple]:
Štefan Schindler
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")

Cambiado en la versión 3.3: Moved Collections Abstract Classes al módulo collections.abc. Por compatibilidad con versiones anteriores, continuarán siendo visibles en este módulo también hasta la versión 3.8, donde dejará 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
fuente
55
¡Guauu! Esto funciona muy bien y es mucho más breve que cualquiera de las otras respuestas correctas. No tenía idea de que los tipos incorporados heredan, collections.Sequencepero lo probé y veo que sí. También lo hace xrange. Aún mejor, esta prueba excluye dict, que tiene ambos __getitem__y __iter__.
Neil Mayhew
¿Alguna idea de por qué el resultado de inspect.getmro(list)no incluye Sequence? ¿Cómo se supone que debemos averiguar qué podemos hacer isinstancecuando getmrono se muestra todo?
Steve Jorgensen
@SteveJorgensen Method Resolution Order define la ruta de búsqueda de clase utilizada por Python para buscar el método correcto para usar en las clases. SequenceEs una clase abstracta.
suzanshakya
3
En Python3, puede reemplazar isinstance (obj, basetring) con isinstance (obj, str), y eso debería funcionar.
Adrian Keister
2
en Python 3 necesita y no es instancia (obj, bytes) ... si desea una lista de cosas, y no solo para enumerar bytes ...
Erik Aronesty
35

Python con sabor PHP:

def is_array(var):
    return isinstance(var, (list, tuple))
Cesar
fuente
66
Python es un lenguaje de tipo pato, por lo que realmente debería verificar si var tiene atributo __getitem__. Además, el nombre es engañoso, ya que también hay un módulo de matriz. Y la var también podría ser un numpy.ndarray o cualquier otro tipo, que tiene __getitem__. Consulte stackoverflow.com/a/1835259/470560 para obtener la respuesta correcta.
Peter
99
@peterhil strtambién tiene __getitem__por lo tanto su cheque no excluyestr
erikbwork
99
También lo hace un dict. Verificar __getitem__es un mal consejo aquí.
Petri
10

En términos generales, el hecho de que una función que itera sobre un objeto funciona tanto en cadenas como en tuplas y listas es más característica que error. Ciertamente puede usar isinstanceo esquivar la escritura para verificar un argumento, pero ¿por qué debería hacerlo?

Eso suena como una pregunta retórica, pero no lo es. La respuesta a "¿por qué debería verificar el tipo de argumento?" probablemente va a sugerir una solución al problema real, no el problema percibido. ¿Por qué es un error cuando se pasa una cadena a la función? Además: si se trata de un error cuando se pasa una cadena a esta función, ¿también es un error si se le pasa algún otro iterable que no sea de lista / tupla? ¿Por qué o por qué no?

Creo que la respuesta más común a la pregunta es que los desarrolladores que escriben f("abc")esperan que la función se comporte como si hubiera escrito f(["abc"]). Probablemente hay circunstancias en las que tiene más sentido proteger a los desarrolladores de sí mismos que respaldar el caso de uso de iterar a través de los caracteres en una cadena. Pero pensaría mucho en ello primero.

Robert Rossney
fuente
16
"Pero pensaría mucho en ello primero". Yo no lo haría Si se supone que la función es una función de lista y, entonces sí, debería tratarlos de la misma manera (es decir, dada una lista, escupirla al revés, cosas así). Sin embargo, si es una función donde uno de los argumentos puede ser una cadena o una lista de cadenas (que es una necesidad bastante común), entonces obligar al desarrollador a usar esa función para ingresar siempre su parámetro dentro de una matriz parece un poco demasiado . Además, piense en cómo manejaría, digamos, la entrada JSON. Definitivamente querrás tratar una lista de objetos diferente de una cadena.
Jordan Reiter
8

Pruebe esto para facilitar la lectura y las mejores prácticas:

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 eso ayude.

Om Prakash
fuente
Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Juha Untinen
1
En Python 3, es: from typing import List-> isinstance([1, 2, 3], List= True, y isinstance("asd", List)= False
Juha Untinen
5

El strobjeto no tiene un __iter__atributo.

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

para que puedas hacer un chequeo

assert hasattr(x, '__iter__')

y esto también generará un AssertionErrorbeneficio para cualquier otro objeto no iterable.

Editar: como Tim menciona en los comentarios, esto solo funcionará en python 2.x, no 3.x

Moe
fuente
8
Cuidado: en Python 3 hasattr('','__iter__')vuelve True. Y, por supuesto, eso tiene sentido ya que puede iterar sobre una cadena.
Tim Pietzcker
1
De Verdad? No lo sabia. Siempre pensé que esta era una solución elegante al problema, bueno.
Moe
1
Esta prueba no funcionó en pyodbc.Row. No tiene iter __ () pero se comporta más o menos como una lista (incluso define "__setitem "). Puede iterar sus elementos muy bien. La función len () funciona y puede indexar sus elementos. Estoy luchando por encontrar la combinación correcta que capture todos los tipos de lista, pero excluya las cadenas. Creo que me conformaré con un control de " getitem " y " len ", excluyendo explícitamente la cadena base.
haridsv
5

Esto no tiene la intención de responder directamente al OP, pero quería compartir algunas ideas relacionadas.

Estaba muy interesado en la respuesta de @steveha anterior, que parecía dar un ejemplo donde la escritura de patos parece romperse. Pensándolo bien, sin embargo, su ejemplo sugiere que es difícil cumplir con la escritura del pato, pero lo hace no sugiere questr merezca un manejo especial.

Después de todo, un no strtipo (por ejemplo, un tipo definido por el usuario que mantiene algunas estructuras recursivas complicadas) puede causar que la sreprfunción @steveha provoque una recursión infinita. Si bien esto es ciertamente poco probable, no podemos ignorar esta posibilidad. Por lo tanto, en lugar de especial-carcasa stren srepr, hay que aclarar lo que queremossrepr hacer cuando un infinito resultados de recursividad.

Puede parecer que un enfoque razonable es simplemente romper la recursión en sreprel momento list(arg) == [arg]. Esto, de hecho, resolvería completamente el problema con str, sin ningunaisinstance .

Sin embargo, una estructura recursiva realmente complicada puede causar un bucle infinito donde list(arg) == [arg]nunca sucede. Por lo tanto, si bien la comprobación anterior es útil, no es suficiente. Necesitamos algo así como un límite estricto en la profundidad de recursión.

Mi punto es que si planea manejar tipos de argumentos arbitrarios, el manejo strmediante la escritura de pato es mucho, mucho más fácil que el manejo de los tipos más generales que puede encontrar (en teoría). Entonces, si siente la necesidad de excluir strinstancias, debería exigir que el argumento sea una instancia de uno de los pocos tipos que especifique explícitamente.

max
fuente
1
Hmm, me gusta cómo piensas. Creo que no puede argumentar que mi código es práctico: hay exactamente un caso común strque maneja el código de caso especial. Pero tal vez debería haber una nueva propiedad estándar que el código pueda inspeccionar, .__atomic__digamos, que indique que algo no puede desglosarse más. Probablemente sea demasiado tarde para agregar otra función incorporada atomic()a Python, pero tal vez podamos agregar from collections import atomico algo.
steveha
5

Encuentro una función llamada is_sequence en 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))

Y he verificado que satisface tus necesidades.

Lerner Zhang
fuente
2

Hago esto en mis casos de prueba.

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

No probado en los generadores, creo que te quedan en el próximo 'rendimiento' si se pasa en un generador, que puede arruinar las cosas aguas abajo. Pero, de nuevo, esta es una 'prueba de unidad'

FlipMcF
fuente
2

En la manera de "escribir pato", ¿qué tal

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

o

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

respectivamente. Esto evita las cosas isinstance/ hasattrintrospección.

También puede verificar viceversa:

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

Todas las variantes en realidad no cambian el contenido de la variable, sino que implican una reasignación. No estoy seguro de si esto podría ser indeseable en algunas circunstancias.

Curiosamente, con la asignación "en el lugar" +=no TypeErrorse plantearía en ningún caso si se lsttrata de una lista (no una tupla ). Es por eso que la tarea se realiza de esta manera. Tal vez alguien pueda arrojar luz sobre por qué es eso.

utobi
fuente
1

forma más simple ... usando anyyisinstance

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

Otra versión de tipeo de pato para ayudar a distinguir los objetos con forma de cadena de otros objetos con secuencia.

La representación de cadena de los objetos en forma de cadena es la cadena misma, por lo que puede verificar si obtiene un objeto igual del strconstructor:

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

Esto debería funcionar para todos los objetos compatibles con stry para todo tipo de objetos iterables.

stevepastelan
fuente
0

Python 3 tiene esto:

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

Entonces, para verificar tanto las Listas como las Tuplas, sería:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
Juha Untinen
fuente
0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
ersh
fuente
2
¿Es esta una buena manera de hacer esto?
ersh
1
Bienvenido a SO. Sería útil una explicación de por qué este código responde a la pregunta.
Nick
Sí, por supuesto, utilizo métodos similares a esto, ya que la tubería se trata como una o más, usted afirma que el tipo debe ser una lista o una tupla de tipo que genera un mensaje de error personalizado para el manejo de errores. Creo que responde la pregunta, pero tenía curiosidad por saber si es una forma efectiva de hacerlo, ya que todavía estoy tratando de acostumbrarme a escribir el código más optimizado. Sin embargo, no estoy seguro de si este código se pierde de cosas que pueden actuar como listas / tuplas, pero que no son subclases de ninguno, ya que la respuesta aceptada aborda esa posibilidad. ¡Gracias!
Ersh
-1

Solo haz esto

if type(lst) in (list, tuple):
    # Do stuff
ATOzTOA
fuente
55
isinstance (lst, (list, tuple))
Davi Lima
@DaviLima OK, esa es otra manera. Pero type () se recomienda para tipos incorporados y es instancia para las clases.
ATOzTOA
-1

en 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
fuente
2
En la última comprobación, usa un tipo str, no una cadena. Intenta isinstance('my_string', collections.abc.Container)y verás que volverá True. Esto se debe a que abc.Containerproporciona el __contains__método, y las cadenas lo tienen, por supuesto.
Georgy
-6

Tiendo a hacer esto (si realmente, realmente tuviera que hacerlo):

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
fuente
55
Casi nunca deberías hacer esto. ¿Qué sucede si soy some_varuna instancia de una clase que es una subclase de list()? Su código no tendrá idea de qué hacer con él, aunque funcionará perfectamente bajo el código "hacer algo con una lista". Y rara vez necesita preocuparse por la diferencia entre una lista y una tupla. Lo siento, -1.
steveha 02 de
1
No hay necesidad de escribir type(tuple()), tuplelo haré. Lo mismo para la lista. Además, ambos stry unicodeextender basestring, que es el tipo de cadena real, por lo que desea verificar eso en su lugar.
trata bien tus modificaciones el
@DrBloodmoney: voto negativo accidental. Por favor (trivialmente) edite su respuesta para permitirme eliminar el voto negativo.
SabreWolfy
La igualdad no parece una comparación significativa para los tipos, para mí. Que había prueba de la identidad en su lugar: type(i) is list. Además, type(list())es solo en listsí mismo ... Finalmente, esto no funciona correctamente con las subclases. Si ies de hecho y OrderedDict, o algún tipo de nombre de tupla, este código tratará como una cadena.
bukzor