Python - 'if foo in dict' vs 'try: dict [foo]'

24

Supongo que se trata menos de la naturaleza de la tipificación de patos y más acerca de mantenerse pitónico.

En primer lugar, cuando se trata de dictados, en particular cuando la estructura del dict es bastante predecible y una clave dada generalmente no está presente, pero a veces lo está, primero pienso en dos enfoques:

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

Y, por supuesto, el enfoque de 'perdón versus permiso' de Ye Olde.

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

Como jornalero de Python, siento que veo que este último prefiere mucho, lo que solo se siente extraño, supongo porque en los documentos de Python try/exceptsparece preferirse cuando hay un error real, en lugar de una, um ... ¿ ausencia de éxito?

Si el dict ocasional no tiene clave en myDict, y se sabe que no siempre tendrá esa clave, ¿ es un try / except contextualmente engañoso? Esto no es un error de programación, es solo un hecho de los datos, este dict simplemente no tenía esa clave en particular.

Esto parece particularmente importante cuando observa la sintaxis try / except / else, que parece ser realmente útil cuando se trata de asegurarse de que try no detecte demasiados errores. Eres capaz de hacer algo como:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

¿No conducirá eso a tragar todo tipo de errores extraños que probablemente sean el resultado de algún código incorrecto? El código anterior podría estar evitando que vea que está tratando de agregar 2 + {}y es posible que nunca se dé cuenta de que una parte de su código ha salido terriblemente mal. No sugiero que debamos verificar todos los tipos, es por eso que es Python y no JavaScript, pero nuevamente con el contexto de try / except, parece que se supone que debe atrapar al programa haciendo algo que no debería estar haciendo, en su lugar de permitirle continuar.

Me doy cuenta de que el ejemplo anterior es una especie de argumento de hombre de paja, y de hecho es intencionalmente malo. Pero dado el credo pitónico de better to ask forgiveness than permissionNo puedo evitar sentir que plantea la pregunta de dónde está realmente la línea en la arena entre la aplicación correcta de if / else vs try / except es, en particular cuando sabes qué esperar de los datos con los que estás trabajando.

Ni siquiera estoy hablando de preocupaciones de velocidad o mejores prácticas aquí, estoy un poco confundido por el diagrama de Venn percibido de casos en los que parece que podría ir de cualquier manera, pero la gente se equivoca al lado de un intento / excepto porque 'alguien en alguna parte dijo que era Pythonic'. ¿He sacado conclusiones erróneas sobre la aplicación de esta sintaxis?


fuente
Estoy de acuerdo en que la tendencia a probar, excepto corre el riesgo de ocultar errores. Mi opinión es que, en general, debe evitar el intento, excepto por las condiciones que espera ver (por supuesto, se aplican algunas excepciones a esta regla).
Gordon Bean

Respuestas:

26

Utilice el método get en su lugar:

some_dict.get(the_key, default_value)

... donde default_valuese devuelve el valor si the_keyno está en some_dict. Si omite default_value, Nonese devuelve si falta la clave.

En general, en Python, las personas tienden a preferir probar / excepto a comprobar primero algo; consulte la entrada de EAFP en el glosario . Tenga en cuenta que muchas funciones de "prueba de membresía" usan excepciones detrás de escena.

desviarse
fuente
Sin embargo, ¿no es este el mismo problema? Si estamos tratando de trabajar con dicty haciendo un trabajo basado en lo que se encuentra, parece más if myDict.get(a_key) is not None: do_work(myDict[a_key])vergonzoso y otro ejemplo de mirar implícitamente antes de saltar, además es un poco menos legible en mi humilde opinión. Quizás no estoy entendiendo dict.get(a_key, a_default)en el contexto correcto, pero parece que es un precursor para escribir 'no-un-cambio-declaración si / elses' o valores mágicos. Sin embargo, me gusta lo que hace este método, lo investigaré más a fondo.
1
@Stick: lo haces: data = myDict.get(a_key, default)y do_workharás lo correcto cuando se te dé default(p. Ej. None) O lo harás if data: do_work(data). Solo se necesita una búsqueda en cualquier caso. (Puede ser if data is not None: ..., en cualquier caso, todavía es solo una búsqueda y luego su propia lógica puede hacerse cargo)
Desvíe el
1
Así que he decidido que dict.get () me ha ahorrado mucho esfuerzo en mi proyecto actual. Ha reducido mi recuento de líneas, ha limpiado una gran cantidad de try/except/elsebasura de aspecto terrible y en general ha mejorado en mi humilde opinión la legibilidad de mi código. Aprendí una valiosa lección que había escuchado antes, pero me las arreglé para no tomar en serio: "Si sientes que estás escribiendo demasiado código, detente y mira las características del lenguaje". ¡¡Gracias!!
@Stick: me alegra escucharlo :) Me gusta ese consejo, nunca lo había escuchado antes.
desviarse
1
Técnicamente, ¿cuál es el más rápido?
Olivier Pons
4

¿La clave faltante es una regla o una excepción ?

Desde un punto de vista pragmático, lanzar / atrapar es mucho más lento que verificar si la clave está en la tabla. Si la clave faltante es una ocurrencia común, debe usar la condición.

Otra cosa a favor de la condición es una cláusula else : es mucho más fácil de entender cuando cualquiera de los bloques se ejecuta, tenga en cuenta que incluso la misma clase de excepción se puede lanzar desde varias declaraciones en el bloque try.

El código en la cláusula excepto no debe ser parte de la falla normal (por ejemplo, si la clave no está allí, agréguela), debe estar manejando el error.

Eugene
fuente
44
"Desde un punto de vista pragmático, lanzar / atrapar es mucho más lento que verificar si la llave está en la mesa" - no, no lo está. No necesariamente, de todos modos. La última vez que revisé, las implementaciones más comunes de Python hacen la versión try/ catchmás rápido.
desvío
2
Lanzar / atrapar en python no es tan importante en términos de rendimiento, en la medida en que a menudo se usa para el control de flujo en python. Además, existe la posibilidad en algunas situaciones en las que ese dict podría modificarse entre la prueba y el acceso.
whatsisname
@whatsisname: pensé en agregar eso a mi respuesta, pero TBH, si tienes riesgos raciales como ese, tienes problemas mucho más grandes que EAFP vs LBYL: P
detly
1

En el espíritu de ser "pitón" y, específicamente, de escribir pato, el try / except me parece casi frágil.

Todo lo que realmente quiere es algo con acceso tipo dict, pero __getitem__puede ser anulado para hacer algo que no es exactamente lo que espera. Por ejemplo, usando defaultdictde la biblioteca estándar:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

Debido a que el valor de retorno predeterminado es Falsy, la comprobación de acceso así funcionará la mayoría de las veces bien. Parece que el código también es más simple, por lo que mis compañeros de trabajo y yo lo hemos hecho durante meses.

Desafortunadamente, unos meses más tarde, estas claves adicionales terminaron causando problemas y errores difíciles de rastrear, porque se 0convirtieron en un valor válido. Y a veces nos gustaría utilizar inpara comprobar si existía una llave, por lo que el código de hacer las cosas diferentes, cuando debería haber sido idénticos.

La razón por la que sugiero que se ve roto es porque, en el espíritu de escritura de pato de Python, todo lo que debe desear es algo parecido a un dict, y se defaultdictajusta perfectamente a ese requisito, como lo haría cualquier objeto del que pueda crear que herede dict. No puede garantizar que la persona que llama no vaya a cambiar su implementación más adelante, por lo que debe hacer lo que tenga menos efectos secundarios.

Use la primera versión if myKey in mydict,.


Además, tenga en cuenta que esto se trata específicamente del ejemplo en la pregunta. El credo de Python de "Es mejor pedir perdón que permiso" tiene como objetivo principal garantizar que realmente actúes correctamente cuando no obtienes lo que quieres. Por ejemplo, verificar si existe un archivo y luego intentar leerlo: al menos 3 cosas que se me ocurren pueden salir mal:

  • Una condición de carrera es que el archivo podría haberse eliminado / movido / etc.
  • No tienes permisos de lectura en el archivo
  • El archivo ha sido dañado

En el primero puede intentar "pedir permiso", pero por la naturaleza de las condiciones de una carrera, no siempre funcionará necesariamente; el segundo es demasiado fácil de olvidar para verificar; y el tercero no puede verificarse sin intentar leer el archivo. En cualquier caso, lo que hagas después de fallar seguramente será el mismo, así que en este ejemplo, es mejor "pedir perdón" usando un try / except.

Izkata
fuente