Python hasattr vs getattr

Últimamente he estado leyendo algunos tweets y la documentación de python sobre hasattr y dice:

hasattr (objeto, nombre)

Los argumentos son un objeto y una cadena. El resultado es Verdadero si la cadena es el nombre de >> uno de los atributos del objeto, Falso si no lo es. (Esto se implementa llamando a getattr (objeto, nombre) y viendo si genera un AttributeError o no).

Hay un lema en Python que dice que es más fácil pedir perdón que un permiso donde generalmente estoy de acuerdo.

Intenté hacer una prueba de rendimiento en este caso con un código de Python realmente simple:

import timeit definition="""\ class A(object): a = 1 a = A() """ stm="""\ hasattr(a, 'a') """ print timeit.timeit(stmt=stm, setup=definition, number=10000000) stm="""\ getattr(a, 'a') """ print timeit.timeit(stmt=stm, setup=definition, number=10000000) 

Con los resultados:

 $ python test.py hasattr(a, 'a') 1.26515984535 getattr(a, 'a') 1.32518696785 

También he intentado lo que sucede si el atributo no existe y las diferencias entre getattr y hasattr son mayores. Entonces, lo que he visto hasta ahora es que getattr es más lento que hasattr, pero en la documentación dice que llama a getattr.

He buscado la implementación CPython de hasattr y getattr y parece que ambos llaman a la siguiente función:

 v = PyObject_GetAttr(v, name); 

pero hay más repetitivo en getattr que en hasattr que probablemente lo hace más lento.

¿Alguien sabe por qué en la documentación decimos que hasattr llama a getattr y parece que animamos a los usuarios a usar getattr en lugar de hasattr cuando en realidad no se debe al rendimiento? ¿Es solo porque es más python?

Tal vez estoy haciendo algo mal en mi prueba 🙂

Gracias,

Raúl

La documentación no alienta, la documentación simplemente establece lo obvio. El hasattr se implementa como tal, y lanzar un AttributeError desde un generador de propiedades puede hacer que parezca que el atributo no existe. Este es un detalle importante, y es por eso que se establece explícitamente en la documentación. Considere por ejemplo este código:

 class Spam(object): sausages = False @property def eggs(self): if self.sausages: return 42 raise AttributeError("No eggs without sausages") @property def invalid(self): return self.foobar spam = Spam() print(hasattr(Spam, 'eggs')) print(hasattr(spam, 'eggs')) spam.sausages = True print(hasattr(spam, 'eggs')) print(hasattr(spam, 'invalid')) 

El resultado es

 True False True False 

Es decir, la clase Spam tiene un descriptor de propiedad para los eggs , pero como el getter genera AttributeError si not self.sausages , entonces la instancia de esa clase no tiene eggshasattr “.

Aparte de eso, use hasattr solo cuando no necesite el valor ; si necesita el valor, use getattr con 2 argumentos y capture la excepción, o 3 argumentos, el tercero es un valor predeterminado razonable.

Los resultados utilizando getattr() (2.7.9):

 >>> spam = Spam() >>> print(getattr(Spam, 'eggs'))  >>> print(getattr(spam, 'eggs')) Traceback (most recent call last): File "", line 1, in  File "", line 7, in eggs AttributeError: No eggs without sausages >>> spam.sausages = True >>> print(getattr(spam, 'eggs')) 42 >>> print(getattr(spam, 'invalid')) Traceback (most recent call last): File "", line 1, in  File "", line 10, in invalid AttributeError: 'Spam' object has no attribute 'invalid' >>> 

Parece que hasattr tiene un problema con las excepciones de deglución (al menos en Python 2.7 ), por lo que probablemente es mejor mantenerse alejado de él hasta que se solucione.

Tomemos, por ejemplo, el siguiente código :

 >>> class Foo(object): ... @property ... def my_attr(self): ... raise ValueError('nope, nope, nope') ... >>> bar = Foo() >>> bar.my_attr Traceback (most recent call last): File "", line 1, in  File "", line 4, in my_attr ValueError: nope, nope, nope >>> hasattr(Foo, 'my_attr') True >>> hasattr(bar, 'my_attr') False >>> getattr(bar, 'my_attr', None) Traceback (most recent call last): File "", line 1, in  File "", line 4, in my_attr ValueError: nope, nope, nope >>>