¿Por qué la lista no tiene un método seguro "get" como diccionario?

264

¿Por qué la lista no tiene un método seguro "get" como el diccionario?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range
CSZ
fuente
1
Las listas se usan para diferentes propósitos que los diccionarios. El get () no es necesario para los casos de uso típicos de la lista. Sin embargo, para el diccionario, get () es bastante útil.
mgronber
42
Siempre puede obtener una sublista vacía de una lista sin generar IndexError si solicita un segmento en l[10:11]su lugar: en lugar de l[10], por ejemplo. () La sublista tendrá el elemento deseado si existe)
jsbueno
56
Al contrario de algunos aquí, apoyo la idea de una caja fuerte .get. Sería el equivalente l[i] if i < len(l) else default, pero más legible, más conciso, y permitiría iser una expresión sin tener que recalcularlo
Paul Draper
66
Hoy deseaba que esto existiera. Uso una función costosa que devuelve una lista, pero solo quería el primer elemento, o Nonesi no existiera. Hubiera sido bueno decirlo x = expensive().get(0, None)para no tener que poner el inútil retorno de costoso en una variable temporal.
Ryan Hiebert
2
@Ryan mi respuesta puede ayudarte a stackoverflow.com/a/23003811/246265
Jake

Respuestas:

112

En última instancia, es probable que no tenga un .getmétodo seguro porque a dictes una colección asociativa (los valores están asociados con los nombres) donde es ineficiente verificar si una clave está presente (y devolver su valor) sin lanzar una excepción, mientras que es super trivial para evitar excepciones al acceder a elementos de la lista (ya que el lenmétodo es muy rápido). El .getmétodo le permite consultar el valor asociado con un nombre, no acceder directamente al elemento 37 en el diccionario (que sería más parecido a lo que está pidiendo de su lista).

Por supuesto, puede implementar esto fácilmente usted mismo:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

Incluso podría incluirlo en el __builtins__.listconstructor __main__, pero eso sería un cambio menos generalizado ya que la mayoría del código no lo usa. Si solo quisiera usar esto con listas creadas por su propio código, simplemente podría subclasificar listy agregar el getmétodo.

Nick Bastin
fuente
24
Python no permite tipos incorporados de parches de mono comolist
Imran
77
@CSZ: .getresuelve un problema que las listas no tienen, una forma eficiente de evitar excepciones al obtener datos que pueden no existir. Es súper trivial y muy eficiente saber qué es un índice de lista válido, pero no hay una forma particularmente buena de hacerlo para los valores clave en un diccionario.
Nick Bastin
10
No creo que se trate de eficiencia en absoluto: verificar si una clave está presente en un diccionario y / o devolver un elemento O(1). No será tan rápido en términos crudos como la comprobación len, pero desde el punto de vista de la complejidad, todos lo son O(1). La respuesta correcta es la típica de uso / semántica ...
Mark Longair
3
@Mark: No todos los O (1) son iguales. Además, dictes solo el mejor caso O (1), no todos los casos.
Nick Bastin
44
Creo que la gente está perdiendo el punto aquí. La discusión no debería ser sobre eficiencia. Deténgase con la optimización prematura. Si su programa es demasiado lento, está abusando .get()o tiene problemas en otra parte de su código (o entorno). El punto de usar tal método es la legibilidad del código. La técnica "vainilla" requiere cuatro líneas de código en cada lugar que se necesita hacer. La .get()técnica solo requiere uno y se puede encadenar fácilmente con llamadas a métodos posteriores (por ejemplo my_list.get(2, '').uppercase()).
Tyler Crompton
67

Esto funciona si quieres el primer elemento, como my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

Sé que no es exactamente lo que pediste, pero podría ayudar a otros.

Jake
fuente
77
menos pitónico que funcional esque-esque
Eric
next(iter(my_list[index:index+1]), 'fail')Permite cualquier índice, no sólo 0. o menos FP, pero sin duda más Pythonic, y es casi seguro que sea más legible: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup
47

Probablemente porque simplemente no tenía mucho sentido para la semántica de listas. Sin embargo, puede crear fácilmente el suyo subclasificando.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()
Keith
fuente
55
Esta es, con diferencia, la respuesta más pitónica al OP. Tenga en cuenta que también puede extraer una sublista, que es una operación segura en Python. Dado mylist = [1, 2, 3], puede intentar extraer el noveno elemento con mylist [8: 9] sin activar una excepción. Luego puede probar si la lista está vacía y, en caso de que no esté vacía, extraiga el elemento individual de la lista devuelta.
jose.angel.jimenez
1
Esta debería ser la respuesta aceptada, no los otros trucos de una línea no pitónicos, especialmente porque conserva la simetría con los diccionarios.
Eric
1
No hay nada de pitónico en subclasificar sus propias listas solo porque necesita un buen getmétodo. La legibilidad cuenta. Y la legibilidad sufre con cada clase adicional innecesaria. Simplemente use el try / exceptenfoque sin crear subclases.
Jeyekomon
@Jeyekomon Es perfectamente pitónico reducir las repeticiones mediante subclases.
Keith el
42

En lugar de usar .get, usar esto debería estar bien para las listas. Solo una diferencia de uso.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

fuente
15
Esto falla si intentamos obtener el último elemento con -1.
pretobomba
Tenga en cuenta que esto no funciona para los objetos de lista enlazados circularmente. Además, la sintaxis provoca lo que me gusta llamar un "bloque de exploración". Al escanear el código para ver qué hace, esta es una línea que me ralentizaría por un momento.
Tyler Crompton
en línea si / si no funciona con python anterior como 2.6 (¿o es 2.5?)
Eric
3
@TylerCrompton: no hay una lista enlazada circularmente en Python. Si escribiste uno por tu cuenta, eres libre de haber implementado un .getmétodo (excepto que no estoy seguro de cómo explicarías lo que significaba el índice en este caso, o por qué alguna vez fallaría).
Nick Bastin el
Una alternativa que maneja los índices negativos fuera de límites seríalst[i] if -len(lst) <= i < len(l) else 'fail'
mic
17

Prueba esto:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'
Vsevolod Kulaga
fuente
1
Me gusta este, aunque primero requiere la creación de una nueva sublista.
Rick apoya a Monica el
15

Créditos a jose.angel.jimenez


Para los fanáticos de "oneliner" ...


Si desea el primer elemento de una lista o si desea un valor predeterminado si la lista está vacía, intente:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

devoluciones a

y

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

devoluciones default


Ejemplos para otros elementos ...

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

Con reserva predeterminada ...

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

Probado con Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

qräbnö
fuente
1
Alternativa a corto: value, = liste[:1] or ('default',). Parece que necesitas los paréntesis.
qräbnö
14

Lo mejor que puede hacer es convertir la lista en un dict y luego acceder a ella con el método get:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')
fabuloso
fuente
20
Una solución razonable, pero difícilmente "lo mejor que puedes hacer".
tripleee
3
Muy ineficiente sin embargo. Nota: En lugar de eso zip range len, uno podría usardict(enumerate(my_list))
Marian
3
Esto no es lo mejor, es lo peor que puedes hacer.
erikbwork
3
Es lo peor si considera el rendimiento ... si le importa el rendimiento, no codifica en un lenguaje interpretado como python. Encuentro esta solución usando un diccionario bastante elegante, potente y pitónico. Las optimizaciones tempranas son malas de todos modos, así que hagamos un dictamen y luego veamos que es un cuello de botella.
Eric
7

Así que investigué un poco más sobre esto y resultó que no hay nada específico para esto. Me emocioné cuando encontré list.index (valor), devuelve el índice de un elemento específico, pero no hay nada para obtener el valor en un índice específico. Entonces, si no desea utilizar la solución safe_list_get, que creo que es bastante buena. Aquí hay algunas declaraciones de 1 liner if que pueden hacer el trabajo por usted dependiendo del escenario:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

También puede usar None en lugar de 'No', lo que tiene más sentido .:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Además, si solo desea obtener el primer o el último elemento de la lista, esto funciona

end_el = x[-1] if x else None

También puede convertirlos en funciones, pero aún así me gustó la solución de excepción IndexError. Experimenté con una versión safe_list_getsimplificada de la solución y la hice un poco más simple (no predeterminada):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

No se ha comparado para ver qué es lo más rápido.

radtek
fuente
1
Realmente no pitónico.
Eric
@Eric, ¿qué fragmento? Creo que el intento, excepto tiene más sentido al mirarlo de nuevo.
radtek
Una función independiente no es pitónica. Las excepciones son un poco más pitónicas, pero no tanto, ya que es un patrón tan común en los lenguajes de programación. Lo que es mucho más pitónico es un nuevo objeto que extiende el tipo incorporado listsubclasificándolo. De esa forma, el constructor puede tomar una listo cualquier cosa que se comporte como una lista, y la nueva instancia se comporte como una list. Vea la respuesta de Keith a continuación, que debería ser la aceptada en mi humilde opinión.
Eric
1
@Eric Analicé la pregunta no como específica de OOP sino como "¿por qué las listas no tienen analogía para dict.get()devolver un valor predeterminado de una referencia de índice de lista en lugar de tener que atraparlo IndexError? Entonces, realmente se trata de la función de idioma / biblioteca (y no de OOP vs contexto FP). Además, uno probablemente tiene que calificar su uso de 'pythonic' como quizás WWGD (como su desdén por FP Python es bien conocido) y no necesariamente solo satisfacer PEP8 / 20.
cowbert
1
el = x[4] if len(x) == 4 else 'No'- quieres decir len(x) > 4? x[4]está fuera de límites si len(x) == 4.
Mic
4

Los diccionarios son para búsquedas. Tiene sentido preguntar si existe una entrada o no. Las listas generalmente se repiten. No es común preguntar si existe L [10] sino más bien si la longitud de L es 11.

Cola de aprendiz
fuente
Sí, de acuerdo contigo. Pero acabo de analizar la URL relativa de la página "/ group / Page_name". Dividirlo por '/' y quería verificar si PageName es igual a cierta página. Sería cómodo escribir algo como [url.split ('/'). Get_from_index (2, None) == "lalala"] en lugar de hacer una comprobación adicional de la longitud o capturar una excepción o escribir una función propia. Probablemente tengas razón, simplemente se considera inusual. De todos modos, todavía estoy en desacuerdo con esto =)
CSZ
@Nick Bastin: No pasa nada. Se trata de simplicidad y velocidad de codificación.
CSZ
También sería útil si quisiera usar listas como un diccionario más eficiente en el espacio en los casos en que las claves son entradas consecutivas. Por supuesto, la existencia de indexación negativa ya detiene eso.
Antimonio
-1

Básicamente, su caso de uso solo es relevante cuando se realizan matrices y matrices de una longitud fija, para que sepa cuánto tiempo tienen de antemano. En ese caso, normalmente también los crea antes de rellenarlos con Ninguno o 0, de modo que de hecho cualquier índice que usará ya existe.

Se podría decir esto: necesito .get () en los diccionarios con bastante frecuencia. Después de diez años como programador a tiempo completo, no creo que alguna vez lo haya necesitado en una lista. :)

Lennart Regebro
fuente
¿Qué tal mi ejemplo en los comentarios? ¿Qué es más simple y legible? (url.split ('/'). getFromIndex (2) == "lalala") O (resultado = url.split (); len (resultado)> 2 y resultado [2] == "lalala"). Y sí, sé que puedo escribir tal función yo mismo =) pero me sorprendió que tal función no esté integrada.
CSZ
1
Yo diría que en tu caso lo estás haciendo mal. El manejo de URL debe hacerse por rutas (coincidencia de patrones) o por recorrido de objetos. Sin embargo, para responder a su caso particular: 'lalala' in url.split('/')[2:]. Pero el problema con su solución aquí es que solo mira el segundo elemento. ¿Qué pasa si la URL es '/ monkeybonkey / lalala'? Obtendrá un Truemensaje aunque la URL no sea válida.
Lennart Regebro
Tomé solo el segundo elemento porque solo necesitaba el segundo elemento. Pero sí, las rebanadas parecen una buena alternativa de trabajo
CSZ
@CSZ: Pero luego se ignora el primer elemento y, en ese caso, puede omitirlo. :) Mira lo que quiero decir, el ejemplo no funciona tan bien en la vida real.
Lennart Regebro