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

Esto es lo que normalmente hago para comprobar que la entrada es una list / tuple , pero no una str . Debido a que muchas veces me topé con errores en los que una función pasa un objeto str por error, y la función objective lo hace for x in lst suponiendo que lst es en realidad una list o tuple .

 assert isinstance(lst, (list, tuple)) 

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

Solo en python 2 (no python 3):

 assert not isinstance(lst, basestring) 

Es lo que realmente quieres, de lo contrario te perderás muchas cosas que actúan como listas, pero no son subclases de list o tuple .

Recuerda que en Python queremos usar “tipografía de pato”. Entonces, cualquier cosa que actúe como una lista puede ser tratada como una lista. Por lo tanto, no compruebe el tipo de lista, solo vea si actúa como una lista.

Pero las cuerdas también actúan como una lista, y a menudo eso no es lo que queremos. ¡Hay veces en que incluso es un problema! Por lo tanto, compruebe explícitamente si hay 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é es eso que isinstance() haciendo el cheque de isinstance() allí? Eso es una especie de hackeo. Pero es esencial.

Esta función se llama a sí misma recursivamente en cualquier cosa que actúe como una lista. Si no manejamos la cadena especialmente, entonces sería tratado como una lista, y dividiríamos un carácter a la vez. Pero entonces la llamada recursiva intentaría 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 a sí misma de forma recursiva hasta que la stack se desbordara.

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 se puede dividir una cadena por debajo del nivel de una cadena de un solo carácter, e incluso una. – La cadena de caracteres actúa como una lista.

Nota: el try / except es la forma más limpia de express 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 arg es una secuencia. En lugar de probar el tipo, probablemente deberíamos probar los comportamientos. Si tiene un método .strip() , es una cadena, así que no lo consideres 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 para __getslice__() pero noté que en la documentación del módulo de collections , el método interesante es __getitem__() ; Esto tiene sentido, así es como se indexa un objeto. Eso parece más fundamental que __getslice__() así que cambié lo anterior.

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

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" 

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 or unicode") 

Modificado en la versión 3.3: Movió las clases base abstractas de colecciones al módulo collections.abc. Por compatibilidad con versiones anteriores, seguirán siendo visibles en este módulo hasta la versión 3.8, donde dejará de funcionar.

Python con sabor PHP:

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

En términos generales, el hecho de que una función que itera sobre un objeto funcione en cadenas así como en tuplas y listas es más una característica que un error. Ciertamente, puede usar isinstance o duck typing 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, ¿es también un error si se le pasa alguna otra 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 hubieran 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 la iteración entre los caracteres de una cadena. Pero primero pensaría mucho en ello.

Intente esto para mejorar la legibilidad 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.

El objeto str no tiene un atributo __iter__

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

para que puedas hacer un cheque

 assert hasattr(x, '__iter__') 

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

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

Esto no pretende 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 tipificación de pato parece romperse. Pensándolo bien, sin embargo, su ejemplo sugiere que es difícil ajustarse a la tipificación de pato, pero no sugiere que str merezca un manejo especial.

Después de todo, un tipo no str (por ejemplo, un tipo definido por el usuario que mantiene algunas estructuras recursivas complicadas) puede causar que la función srepr srepr provoque una recursión infinita. Si bien esto es bastante improbable, no podemos ignorar esta posibilidad. Por lo tanto, en lugar de la srepr especial str en srepr , deberíamos aclarar lo que queremos que srepr haga cuando se produce una recursión infinita.

Puede parecer que un enfoque razonable es simplemente romper la recursión en srepr la list(arg) == [arg] momentos list(arg) == [arg] . De hecho, esto resolvería completamente el problema con str , sin ninguna isinstance .

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

Mi punto es que si planea manejar tipos de argumentos arbitrarios, el manejo de str mediante la tipificación de pato es mucho más fácil que manejar los tipos más generales que puede (teóricamente) encontrar. Entonces, si sientes la necesidad de excluir instancias de str , deberías exigir que el argumento sea una instancia de uno de los pocos tipos que especificas explícitamente.

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 comprobado que satisface tus necesidades.

Hago esto en mis testcases.

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

Si no se ha probado en los generadores, creo que quedará en el próximo ‘rendimiento’ si se pasa en un generador, lo que puede arruinar las cosas río abajo Pero, de nuevo, esto es un ‘test de unidad’

De la manera más sencilla … usando any e 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 

En forma de “pato escribiendo”, ¿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 de introspección isinstance / hasattr .

También puede comprobar viceversa:

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

Todas las variantes no cambian realmente el contenido de la variable, pero implican una reasignación. No estoy seguro de que esto sea indeseable en algunas circunstancias.

Curiosamente, con la asignación “en el lugar” += no TypeError generaría en cualquier caso si lst es una lista (no una tupla ). Es por eso que la tarea se hace de esta manera. Tal vez alguien puede arrojar luz sobre por qué es eso.

Solo haz esto

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

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 si hay listas y tuplas, sería:

 from typing import List, Tuple def isit(value): return isinstance(value, List) or isinstance(value, Tuple) 
 assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string" 

Tiendo a hacer esto (si realmente lo tuviera que hacer):

 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