if hasattr(obj, 'attribute'):
# do somthing
vs
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
¿Cuál debería preferirse y por qué?
Respuestas:
hasattr
Realiza interna y rápidamente la misma tarea que el try/except
bloque: 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.
hasattr
va a capturar todas las excepciones en Python 2.x Vea mi respuesta para ver un ejemplo y la solución trivial.
try
puede transmitir que la operación debería funcionar. Aunque try
la intención no siempre es tal, es común, por lo que podría considerarse más legible.
¿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
try
es aproximadamente el doble de rápido que hasattr()
. Si no es así, try
es 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, try
casi 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.
Existe una tercera alternativa, que a menudo es mejor:
attr = getattr(obj, 'attribute', None)
if attr is not None:
print attr
Ventajas:
getattr
no tiene el mal comportamiento de deglución de excepciones señalado por Martin Geiser : en las antiguas Pythons, hasattr
incluso se traga un KeyboardInterrupt
.
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.
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).
Es más corto que try/finally
y, a menudo, más corto que hasattr
.
Un except AttributeError
bloque amplio puede atrapar algo diferente AttributeErrors
al que está esperando, lo que puede generar un comportamiento confuso.
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.attribute
está configurado en Ninguno, deberá usar un valor centinela diferente.
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__
: hasattr
se capturar todas las excepciones en lugar de coger simplemente AttributeError
como usted espera. En otras palabras, el siguiente código se imprimirá b: False
aunque sería más apropiado ver una ValueError
excepció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 hasattr
ahora 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 getattr
ocuparemos de la situación y luego podemos generar la excepción apropiada.
hasattr
al menos no se atrape, KeyboardInterrupt
etc.
safehasattr
, simplemente use getattr
para copiar el valor en una variable local si lo va a usar, lo que casi siempre lo hace.
hasattr
había mejorado así.
hasattr
, y fui a comprobarlo. Tuvimos algunos errores bzr divertidos donde hasattr simplemente se tragó ^ C.
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.
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).
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
# 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
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').
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 .
El primero.
Cuanto más corto, mejor. Las excepciones deben ser excepcionales.
for
declaración y también hasattr
usa 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.
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 hasattr
solo usa el primer caso para determinar el resultado. Comida para el pensamiento ;-)