¿La mejor manera de manejar list.index (podría no existir) en Python?

113

Tengo un código que se parece a esto:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

ok, eso está simplificado, pero entiendes la idea. Es thingposible que ahora no esté en la lista, en cuyo caso quiero pasar -1 como thing_index. En otros idiomas, esto es lo que esperaría index()devolver si no pudiera encontrar el elemento. De hecho arroja un ValueError.

Yo podría hacer esto:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

Pero esto se siente sucio, además de que no sé si se ValueErrorpodría plantear por alguna otra razón. Se me ocurrió la siguiente solución basada en funciones de generador, pero parece un poco compleja:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

¿Existe una forma más limpia de lograr lo mismo? Supongamos que la lista no está ordenada.

Draemon
fuente
4
"... en cuyo caso quiero pasar -1 como thing_index". - Esto definitivamente no es pitónico. Pasar un valor de token (sin sentido) en caso de que una operación no tenga éxito está mal visto; las excepciones realmente son la forma correcta aquí. Especialmente porque thing_list[-1]es una expresión válida, es decir, la última entrada en la lista.
Tim Pietzcker
@jellybean: facepalm ... detecta el codificador de Java: P
Draemon
4
@Tim: hay un str.findmétodo que hace exactamente eso: regresa -1cuando la aguja no se encuentra en el sujeto.
SilentGhost
@Tim None sería mejor entonces ... y esto sería análogo a dict [key] vs dict.get [key]
Draemon
@SilentGhost: Hm, interesante. Puede que tenga que analizar esto con más detalle. str.index()lanza una excepción si no se encuentra la cadena de búsqueda.
Tim Pietzcker

Respuestas:

65

No hay nada "sucio" en el uso de la cláusula try-except. Esta es la forma pitónica. ValueErrorsolo se generará con el .indexmétodo, ¡porque es el único código que tiene allí!

Para responder al comentario:
en Python, es más fácil pedir perdón que obtener permiso, la filosofía está bien establecida, y no index generará este tipo de error para otros problemas. No es que pueda pensar en ninguno.

SilentGhost
fuente
29
Seguramente las excepciones son para casos excepcionales, y esto difícilmente es eso. No tendría ese problema si la excepción fuera más específica que ValueError.
Draemon
1
Sé que solo se puede lanzar con ese método, pero ¿se garantiza que solo se lanzará por esa razón ? No es que pueda pensar en otra razón por la que el índice fallaría ... pero, ¿no son excepciones exactamente para aquellas cosas en las que quizás no pienses?
Draemon
4
¿No es {}.get(index, '')más pitónico? Por no hablar de más corto más legible.
Esteban Küber
1
Utilizo dict [key] cuando espero que exista la clave y dict.get (key) cuando no estoy seguro, y estoy buscando algo equivalente aquí. Devolver en Nonelugar de -1 estaría bien, pero como usted mismo comentó, str.find () devuelve -1, entonces, ¿por qué no debería haber list.find () que haga lo mismo? No estoy de acuerdo con el argumento "pitónico"
Draemon
3
Pero el punto es que la solución más pitónica es usar solo try / except y no el valor centinela -1 en absoluto. Es decir, deberías reescribir otherfunction. Por otro lado, si no está roto, ...
Andrew Jaffe
53
thing_index = thing_list.index(elem) if elem in thing_list else -1

Una línea. Sencillo. Sin excepciones.

Emil Ivanov
fuente
35
Sí, simple, pero eso hará dos búsquedas lineales y, aunque el rendimiento no es un problema per se, parece excesivo.
Draemon
4
@Draemon: De acuerdo, eso hará 2 pases, pero es poco probable que de una base de código de mil líneas, este sea el cuello de botella. :) Siempre se puede optar por una solución imperativa con for.
Emil Ivanov
con lambdindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa Akiel
17

El dicttipo tiene una getfunción , donde si la clave no existe en el diccionario, el segundo argumento getes el valor que debe devolver. De manera similar, hay setdefault, que devuelve el valor en el dictsi la clave existe; de ​​lo contrario, establece el valor de acuerdo con su parámetro predeterminado y luego devuelve su parámetro predeterminado.

Puede extender el listtipo para tener un getindexdefaultmétodo.

class SuperDuperList(list):
    def getindexdefault(self, elem, default):
        try:
            thing_index = self.index(elem)
            return thing_index
        except ValueError:
            return default

Que luego podría usarse como:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )
Ross Rogers
fuente
6

No hay nada de malo en el código que usa ValueError. Aquí hay otro resumen si desea evitar excepciones:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
jfs
fuente
¿Eso es Python 2.6? Sé que no lo mencioné, pero estoy usando 2.5. Esto es probablemente lo que haría en 2.6
Draemon
1
@Draemon: Sí, la next()función existe en Python 2.6+. Pero es fácil de implementar para 2.5, consulte la implementación de la función next () para Python 2.5
jfs
4

Este problema es de filosofía del lenguaje. En Java, por ejemplo, siempre ha existido la tradición de que las excepciones sólo deberían utilizarse en "circunstancias excepcionales", es decir, cuando se han producido errores, en lugar de para el control de flujo . Al principio, esto fue por razones de rendimiento, ya que las excepciones de Java eran lentas, pero ahora este se ha convertido en el estilo aceptado.

Por el contrario, Python siempre ha usado excepciones para indicar el flujo normal del programa, como generar un ValueErrorcomo estamos discutiendo aquí. No hay nada "sucio" en esto en estilo Python y hay muchos más de donde vino eso. Un ejemplo aún más común es la StopIterationexcepción que se genera mediante el next()método de un iterador para indicar que no hay más valores.

Tendayi Mawushe
fuente
En realidad, el JDK lanza manera demasiadas excepciones controladas, así que no estoy seguro de que la filosofía se aplica realmente a Java. No tengo un problema per se StopIterationporque está claramente definido lo que significa la excepción. ValueErrores demasiado genérico.
Draemon
Me refería a la idea de que las excepciones no deben usarse para el control de flujo: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , no tanto a la cantidad de excepciones marcadas que tiene Java, que es otra discusión completamente diferente: mindview.net/Etc/Discussions / CheckedExceptions
Tendayi Mawushe
4

Si lo hace con frecuencia, es mejor que lo elimine en una función de ayuda:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 
Veneet Reddy
fuente
4

¿Qué pasa con esto?:

li = [1,2,3,4,5] # create list 

li = dict(zip(li,range(len(li)))) # convert List To Dict 
print( li ) # {1: 0, 2: 1, 3: 2, 4:3 , 5: 4}
li.get(20) # None 
li.get(1)  # 0 
Alaa Akiel
fuente
1

¿Qué pasa con esto?

otherfunction(thing_collection, thing)

En lugar de exponer algo tan dependiente de la implementación como un índice de lista en una interfaz de función, pase la colección y la cosa y deje que otra función se ocupe de los problemas de "prueba de pertenencia". Si se escribe otra función para que sea independiente del tipo de colección, probablemente comenzaría con:

if thing in thing_collection:
    ... proceed with operation on thing

que funcionará si thing_collection es una lista, tupla, conjunto o dict.

Esto posiblemente sea más claro que:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

que es el código que ya tiene en otra función.

PaulMcG
fuente
1

¿Qué tal esto?

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1
Jie Xiong
fuente
0

Tengo el mismo problema con el método ".index ()" en las listas. No tengo ningún problema con el hecho de que arroja una excepción, pero estoy totalmente en desacuerdo con el hecho de que es un ValueError no descriptivo. Sin embargo, podría entender si hubiera sido un IndexError.

Puedo ver por qué devolver "-1" también sería un problema porque es un índice válido en Python. Pero de manera realista, nunca espero que un método ".index ()" devuelva un número negativo.

Aquí va una línea (vale, es una línea bastante larga ...), recorre la lista exactamente una vez y devuelve "Ninguno" si no se encuentra el elemento. Sería trivial reescribirlo para devolver -1, si así lo desea.

indexOf = lambda list, thing: \
            reduce(lambda acc, (idx, elem): \
                   idx if (acc is None) and elem == thing else acc, list, None)

Cómo utilizar:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>
haavee
fuente
-2

No sé por qué debería pensar que está sucio ... ¿por la excepción? si quieres un delineador, aquí está:

thing_index = thing_list.index(elem) if thing_list.count(elem) else -1

pero desaconsejaría su uso; Creo que la solución de Ross Rogers es la mejor, use un objeto para encapsular su comportamiento deseado, no intente llevar el lenguaje al límite a costa de la legibilidad.

Alan Franzoni
fuente
1
Sí, por la excepción. Su código hará dos búsquedas lineales, ¿no es así? No es que el rendimiento realmente importe aquí. La solución SuperDuperList es buena, pero parece excesiva en esta situación particular. Creo que terminaré capturando la excepción, pero quería ver si había una forma más limpia (para mi estética).
Draemon
@Draemon: bueno, encapsularás el código que tienes en la find()función y estará todo limpio;)
SilentGhost
1
Es curioso que mi respuesta tenga dos votos negativos, mientras que la de Emil Ivanov, aunque semánticamente idéntica, es una de las más votadas. Lo más probable es que esto suceda porque el mío es más lento, ya que empleé count () en lugar del operador "in" ... al menos un comentario que dice que habría sido genial, aunque :-)
Alan Franzoni
-2

Sugeriría:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)
Jonas
fuente
2
El problema con esta solución es que "-1" es un índice válido en la lista (último índice; el primero desde el final). Una mejor manera de manejar esto sería devolver False en la primera rama de su condición.
FanaticD