hasattr () vs bloque try-except para tratar con atributos inexistentes

if hasattr(obj, 'attribute'): # do somthing 

vs

 try: # access obj.attribute except AttributeError, e: # deal with AttributeError 

¿Cuál debería ser la preferida y por qué?

hasattr interna y rápida la misma tarea que el bloque try/except : es una herramienta muy específica, optimizada, de una sola tarea y, por lo tanto, debería ser preferida, cuando corresponda, a la alternativa de propósito muy general.

¿Algún banco que ilustre la diferencia en el rendimiento?

timeit es tu amigo

 $ python -mtimeit -s 'class C(object): a = 4 c = C()' 'hasattr(c, "nonexistent")' 1000000 loops, best of 3: 1.87 usec per loop $ python -mtimeit -s 'class C(object): a = 4 c = C()' 'hasattr(c, "a")' 1000000 loops, best of 3: 0.446 usec per loop $ python -mtimeit -s 'class C(object): a = 4 c = C()' 'try: ca except: pass' 1000000 loops, best of 3: 0.247 usec per loop $ python -mtimeit -s 'class C(object): a = 4 c = C()' 'try: c.nonexistent except: pass' 100000 loops, best of 3: 3.13 usec per loop $ |positive|negative hasattr| 0.446 | 1.87 try | 0.247 | 3.13 

Casi siempre uso hasattr : es la opción correcta para la mayoría de los casos.

El caso problemático es cuando una clase reemplaza __getattr__ : hasattr detectará todas las excepciones en lugar de capturar solo AttributeError como usted espera. En otras palabras, el código siguiente imprimirá b: False , aunque sería más apropiado ver una excepción ValueError :

 class X(object): def __getattr__(self, attr): if attr == 'a': return 123 if attr == 'b': raise ValueError('important error from your database') raise AttributeError x = X() print 'a:', hasattr(x, 'a') print 'b:', hasattr(x, 'b') print 'c:', hasattr(x, 'c') 

El error importante ha desaparecido. Esto se ha corregido en Python 3.2 (número 9666 ) donde hasattr ahora solo hasattr AttributeError .

Una solución fácil es escribir una función de utilidad como esta:

 _notset = object() def safehasattr(thing, attr): return getattr(thing, attr, _notset) is not _notset 

Esto nos permite lidiar con la situación y luego puede generar la excepción apropiada.

Hay una tercera alternativa, ya menudo mejor:

 attr = getattr(obj, 'attribute', None) if attr is not None: print attr 

Ventajas:

  1. getattr no tiene el mal comportamiento de deglución de excepciones señalado por Martin Geiser : en los antiguos Pythons, hasattr incluso se tragará un hasattr KeyboardInterrupt .

  2. La razón normal por la que está verificando si el objeto tiene un atributo es para que pueda usar el atributo, y esto naturalmente conduce a él.

  3. El atributo se lee de forma atómica y está a salvo de otros subprocesos que cambian el objeto. (Sin embargo, si esta es una preocupación importante, es posible que desee considerar bloquear el objeto antes de acceder a él).

  4. Es más corto que try/finally y, a menudo, más corto que hasattr .

  5. Un bloque amplio, except AttributeError , puede capturar otros AttributeErrors distintos a los que espera, lo que puede llevar a un comportamiento confuso.

  6. Acceder a un atributo es más lento que acceder a una variable local (especialmente si no es un atributo de instancia simple). (Aunque, para ser honesto, la microoptimización en Python es a menudo una tarea de tontos).

Una cosa de la que hay que tener cuidado es si le preocupa el caso en el que obj.attribute esté establecido en Ninguno, deberá usar un valor de centinela diferente.

Diría que depende de si su función puede aceptar objetos sin el atributo por diseño , por ejemplo, si tiene dos llamadores a la función, uno proporciona un objeto con el atributo y el otro proporciona un objeto sin él.

Si el único caso en el que obtendrá un objeto sin el atributo se debe a algún error, recomendaría usar el mecanismo de excepciones aunque sea más lento, porque creo que es un diseño más limpio.

Conclusión: creo que es un problema de diseño y legibilidad en lugar de un problema de eficiencia.

Si solo estás probando un atributo, yo diría que hasattr . Sin embargo, si está haciendo varios accesos a atributos que pueden existir o no, entonces usar un bloque try puede ahorrarle algo de escritura.

Si no tener el atributo no es una condición de error, la variante de manejo de excepciones tiene un problema: también detectaría AttributeErrors que podrían venir internamente al acceder a obj.attribute (por ejemplo, porque el atributo es una propiedad por lo que acceder a él llama a algún código).

Sugeriría la opción 2. La opción 1 tiene una condición de carrera si algún otro hilo agrega o elimina el atributo.

Además, python tiene una Modificación , que EAFP (‘más fácil pedir perdón que permiso’) es mejor que LBYL (‘mira antes de saltar’).

Desde un punto de vista práctico, en la mayoría de los idiomas, el uso de un condicional siempre será mucho más rápido que manejar una excepción.

Si desea manejar el caso de un atributo que no existe en algún lugar fuera de la función actual, la excepción es la mejor manera de hacerlo. Un indicador de que es posible que desee utilizar una excepción en lugar de un condicional es que el condicional simplemente establece un indicador y anula la operación actual, y algo en otro lugar comprueba este indicador y toma medidas en función de eso.

Dicho esto, como señala Rax Olgud, la comunicación con otros es un atributo importante del código, y lo que quiere decir al decir “esta es una situación excepcional” en lugar de “esto es algo que espero que suceda” puede ser más importante .

Este tema se trató en la charla EuroPython 2016 Escribiendo más rápido Python por Sebastian Witowski. Aquí está una reproducción de su diapositiva con el resumen de rendimiento. También usa el aspecto terminológico antes de saltar en esta discusión, vale la pena mencionar aquí para etiquetar esa palabra clave.

Si el atributo realmente falta, pedir perdón será más lento que pedir permisos. Por lo tanto, como regla general, puede utilizar la forma de pedir permiso si sabe que es muy probable que falte el atributo u otros problemas que pueda predecir. De lo contrario, si espera que el código dé como resultado la mayoría de las veces el código legible

3 ¿PERMISOS O PERDÓN?

 # CASE 1 -- Attribute Exists class Foo(object): hello = 'world' foo = Foo() if hasatter(foo, 'hello'): foo.hello ## 149ns ## try: foo.hello except AttributeError: pass ## 43.1 ns ## ## 3.5 times faster # CASE 2 -- Attribute Absent class Bar(object): pass bar = Bar() if hasattr(bar, 'hello'): bar.hello ## 428 ns ## try: bar.hello except AttributeError : pass ## 536 ns ## ## 25% slower 

El primero.

Cuanto más corto es mejor. Las excepciones deben ser excepcionales.

Al menos cuando se trata de lo que está sucediendo en el progtwig, omitiendo la parte humana de la legibilidad, etc. (que en realidad es la mayoría de las veces más importante que el rendimiento (al menos en este caso, con ese intervalo de rendimiento), como señalaron Roee Adler y otros).

Sin embargo, mirándolo desde esa perspectiva, se convierte en una cuestión de elegir entre

 try: getattr(obj, attr) except: ... 

y

 try: obj.attr except: ... 

ya que hasattr solo usa el primer caso para determinar el resultado. Comida para el pensamiento 😉