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

Respuestas:

82

hasattrRealiza interna y rápidamente la misma tarea que el try/exceptbloque: es una herramienta muy específica, optimizada y de una sola tarea y, por lo tanto, debe preferirse, cuando corresponda, a la alternativa de propósito muy general.

Alex Martelli
fuente
8
Excepto que todavía necesita el bloque try / catch para manejar las condiciones de carrera (si está usando subprocesos).
Douglas Leeder
1
O, el caso especial que acabo de encontrar: un django OneToOneField sin valor: hasattr (obj, field_name) devuelve False, pero hay un atributo con field_name: solo genera un error DoesNotExist.
Matthew Schinckel
3
Tenga en cuenta que hasattrva a capturar todas las excepciones en Python 2.x Vea mi respuesta para ver un ejemplo y la solución trivial.
Martin Geisler
5
Un comentario interesante : trypuede transmitir que la operación debería funcionar. Aunque tryla intención no siempre es tal, es común, por lo que podría considerarse más legible.
Ioannis Filippidis
88

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

tiempo 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:
 c.a
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
ZeD
fuente
16
+1 por proporcionar números interesantes y tangibles. De hecho, el "intento" es eficiente cuando contiene el caso común (es decir, cuando una excepción de Python es realmente excepcional).
Eric O Lebigot
No estoy seguro de cómo interpretar estos resultados. ¿Qué es más rápido aquí y cuánto?
Stevoisiak
2
@ StevenM.Vascellaro: Si el atributo existe, tryes aproximadamente el doble de rápido que hasattr(). Si no es así, tryes aproximadamente 1,5 veces más lento que hasattr()(y ambos son sustancialmente más lentos que si el atributo existiera). Esto probablemente se deba a que, en el camino feliz, trycasi no hace nada (Python ya está pagando los gastos generales de las excepciones independientemente de si las usa), pero hasattr()requiere una búsqueda de nombre y una llamada a la función. En la ruta infeliz, ambos tienen que hacer un manejo de excepciones y a goto, pero hasattr()lo hacen en C en lugar de código de bytes Python.
Kevin
24

Existe una tercera alternativa, que a menudo es mejor:

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

Ventajas:

  1. getattrno tiene el mal comportamiento de deglución de excepciones señalado por Martin Geiser : en las antiguas Pythons, hasattrincluso se traga un KeyboardInterrupt.

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

  3. El atributo se lee atómicamente y está a salvo de otros hilos 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/finallyy, a menudo, más corto que hasattr.

  5. Un except AttributeErrorbloque amplio puede atrapar algo diferente AttributeErrorsal que está esperando, lo que puede generar 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 tontería).

Una cosa con la que debe tener cuidado es que si le preocupa el caso en el que obj.attributeestá configurado en Ninguno, deberá usar un valor centinela diferente.

poolie
fuente
1
+1 - Esto está de acuerdo con dict.get ('my_key', 'default_value') y debería ser más conocido sobre
1
Excelente para el caso de uso común en el que desea verificar la existencia y usar el atributo con el valor predeterminado.
dsalaj
18

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

El caso problemático es cuando una clase tiene prioridad __getattr__: hasattrse capturar todas las excepciones en lugar de coger simplemente AttributeErrorcomo usted espera. En otras palabras, el siguiente código se imprimirá b: Falseaunque sería más apropiado ver una ValueErrorexcepción:

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

Así ha desaparecido el importante error. Esto se ha solucionado en Python 3.2 ( problema9666 ) donde hasattrahora solo se detectaAttributeError .

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 getattrocuparemos de la situación y luego podemos generar la excepción apropiada.

Martin Geisler
fuente
2
Esto también se mejoró un poco en Python2.6 para que hasattral menos no se atrape, KeyboardInterruptetc.
poolie
O, en lugar de safehasattr, simplemente use getattrpara copiar el valor en una variable local si lo va a usar, lo que casi siempre lo hace.
Poolie
@poolie Eso está bien, no sabía que se hasattrhabía mejorado así.
Martin Geisler
Si, es bueno. Yo tampoco lo sabía hasta hoy, cuando estaba a punto de decirle a alguien que evitara hasattr, y fui a comprobarlo. Tuvimos algunos errores bzr divertidos donde hasattr simplemente se tragó ^ C.
Poolie
Tenía problemas al actualizar 2.7 a 3.6. Esta respuesta me ayuda a comprender y resolver el problema.
Kamesh Jungi
13

Yo 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 que proporciona un objeto con el atributo y el otro que 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.

En pocas palabras: creo que es un problema de diseño y legibilidad más que un problema de eficiencia.

Roee Adler
fuente
1
+1 por insistir en por qué "probar" tiene un significado para las personas que leen el código. :)
Eric O Lebigot
5

Si no tener el atributo no es una condición de error, la variante de manejo de excepciones tiene un problema: detectaría también AttributeErrors que podrían aparecer 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).

TíoZeiv
fuente
este es un problema importante que, en mi opinión, se ha ignorado en gran medida.
Rick apoya a Monica
5

Este tema fue tratado en la charla EuroPython 2016 Escribiendo Python más rápido por Sebastian Witowski. Aquí hay una reproducción de su diapositiva con el resumen de la actuación. También usa el aspecto de terminología antes de saltar en esta discusión, que 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. Entonces, como regla general, puede usar 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 resulte en la mayoría de las veces, el código será 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
jxramos
fuente
4

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

n8gray
fuente
3

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

También pitón tiene un Idiom , que EAFP ( 'más fácil pedir perdón que pedir permiso') es mejor que LBYL ( 'mirar antes de saltar').

Douglas Leeder
fuente
2

Desde un punto de vista práctico, en la mayoría de los lenguajes, usar un condicional siempre será considerablemente 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 usar una excepción en lugar de un condicional es que el condicional simplemente establece una bandera y aborta la operación actual, y algo en otra parte verifica esta bandera y toma medidas basadas en eso.

Dicho esto, como señala Rax Olgud, la comunicación con los demás 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 .

cjs
fuente
+1 por insistir en el hecho de que "intentar" puede interpretarse como "esta es una situación excepcional", en comparación con la prueba condicional. :)
Eric O Lebigot
0

El primero.

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

Desconocido
fuente
5
Las excepciones son muy comunes en Python: hay una al final de cada fordeclaración y también hasattrusa una. Sin embargo, "más corto es mejor" (¡y "más simple es mejor"!) SÍ se aplica, por lo que es preferible el hasattr más simple, más corto y más específico.
Alex Martelli
@Alex solo porque el analizador de Python transforma esas declaraciones para tener 1 no significa que sea muy común. Hay una razón por la que hicieron ese azúcar sintáctico: para que no te quedes atrapado con la crudeza de escribir el intento excepto el bloque.
Desconocido
Si la excepción es excepcional, entonces "explícito es mejor", y la segunda opción del cartel original es mejor, diría…
Eric O Lebigot
0

Al menos cuando se trata de lo que está sucediendo en el programa, 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 lapso de rendimiento), como señalaron Roee Adler y otros).

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

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

y

try: obj.attr
except: ...

ya que hasattrsolo usa el primer caso para determinar el resultado. Comida para el pensamiento ;-)

Levita
fuente