python getattr example
¿Qué es getattr() exactamente y cómo lo uso? (10)
A veces utilizo getattr(..)
para inicializar perezosamente atributos de importancia secundaria justo antes de que se usen en el código.
Compara lo siguiente:
class Graph(object):
def __init__(self):
self.n_calls_to_plot = 0
#...
#A lot of code here
#...
def plot(self):
self.n_calls_to_plot += 1
A esto:
class Graph(object):
def plot(self):
self.n_calls_to_plot = 1 + getattr(self, "n_calls_to_plot", 0)
La ventaja de la segunda forma es que n_calls_to_plot
solo aparece alrededor del lugar en el código donde se usa. Esto es bueno para la legibilidad, porque (1) puede ver inmediatamente con qué valor comienza al leer cómo se usa, (2) no introduce una distracción en el __init__(..)
, que idealmente debería ser sobre el estado conceptual de la clase, en lugar de algún contador de utilidad que solo es usado por uno de los métodos de la función por razones técnicas, como la optimización, y no tiene nada que ver con el significado del objeto.
Estaba leyendo acerca de la función getattr()
. El problema es que todavía no puedo entender la idea de su uso. Lo único que entiendo de getattr()
es que getattr(li, "pop")
es lo mismo que llamar a li.pop
.
No entendí cuando el libro mencionó cómo lo usas para obtener una referencia a una función sin saber su nombre hasta el tiempo de ejecución. Tal vez este soy yo siendo un noob en programación, en general. ¿Alguien podría arrojar algo de luz sobre el tema? ¿Cuándo y cómo uso esto exactamente?
Aparte de todas las sorprendentes respuestas aquí, hay una manera de usar getattr
para guardar abundantes líneas de código y mantenerlo cómodo. Este pensamiento vino después de la terrible representación de código que a veces puede ser una necesidad.
Guión
Supongamos que la estructura de su directorio es la siguiente:
- superheroes.py
- properties.py
Y, tienes funciones para obtener información sobre Thor
, Iron Man
, Doctor Strange
en superheroes.py
. Usted escribe muy inteligentemente las propiedades de todos ellos en properties.py
en un dict
compacto y luego accede a ellos.
properties.py
thor = {
''about'': ''Asgardian god of thunder'',
''weapon'': ''Mjolnir'',
''powers'': [''invulnerability'', ''keen senses'', ''vortex breath''], # and many more
}
iron_man = {
''about'': ''A wealthy American business magnate, playboy, and ingenious scientist'',
''weapon'': ''Armor'',
''powers'': [''intellect'', ''armor suit'', ''interface with wireless connections'', ''money''],
}
doctor_strange = {
''about'': '' primary protector of Earth against magical and mystical threats'',
''weapon'': ''Magic'',
''powers'': [''magic'', ''intellect'', ''martial arts''],
}
Ahora, digamos que desea devolver las capacidades de cada uno de ellos a pedido en superheroes.py
. Por lo tanto, hay funciones como
from .properties import thor, iron_man, doctor_strange
def get_thor_weapon():
return thor[''weapon'']
def get_iron_man_bio():
return iron_man[''about'']
def get_thor_powers():
return thor[''powers'']
... y más funciones que devuelven diferentes valores basados en las teclas y superhéroes.
Con la ayuda de getattr
, podrías hacer algo como:
from . import properties
def get_superhero_weapon(hero):
superhero = getattr(properties, hero)
return superhero[''weapon'']
def get_superhero_powers(hero):
superhero = getattr(properties, hero)
return superhero[''powers'']
¡Redujiste considerablemente la cantidad de líneas de código, funciones y repetición!
Ah, y por supuesto, si tiene nombres erróneos como properties_of_thor
para las variables, puede hacerlas y acceder a ellas simplemente haciendo
def get_superhero_weapon(hero):
superhero = ''properties_of_{}''.format(hero)
all_properties = getattr(properties, superhero)
return all_properties[''weapon'']
NOTA: Para este problema en particular, puede haber formas más inteligentes de lidiar con la situación, pero la idea es dar una idea sobre el uso de getattr
en los lugares correctos para escribir un código más limpio.
Aquí hay un ejemplo rápido y sucio de cómo una clase podría disparar diferentes versiones de un método de guardado, dependiendo de qué sistema operativo se esté ejecutando al usar getattr()
.
import os
class Log(object):
def __init__(self):
self.os = os.name
def __getattr__(self, name):
""" look for a ''save'' attribute, or just
return whatever attribute was specified """
if name == ''save'':
try:
# try to dynamically return a save
# method appropriate for the user''s system
return getattr(self, self.os)
except:
# bail and try to return
# a default save method
return getattr(self, ''_save'')
else:
return getattr(self, name)
# each of these methods could have save logic specific to
# the system on which the script is executed
def posix(self): print ''saving on a posix machine''
def nt(self): print ''saving on an nt machine''
def os2(self): print ''saving on an os2 machine''
def ce(self): print ''saving on a ce machine''
def java(self): print ''saving on a java machine''
def riscos(self): print ''saving on a riscos machine''
def _save(self): print ''saving on an unknown operating system''
def which_os(self): print os.name
Ahora usemos esta clase en un ejemplo:
logger = Log()
# Now you can do one of two things:
save_func = logger.save
# and execute it, or pass it along
# somewhere else as 1st class:
save_func()
# or you can just call it directly:
logger.save()
# other attributes will hit the else
# statement and still work as expected
logger.which_os()
Con bastante frecuencia, cuando estoy creando un archivo XML a partir de datos almacenados en una clase, con frecuencia recibiría errores si el atributo no existiera o fuera del tipo None
. En este caso, mi problema no era saber cuál era el nombre del atributo, como se indica en su pregunta, sino que los datos se almacenaron en ese atributo.
class Pet:
def __init__(self):
self.hair = None
self.color = None
Si usara hasattr
para hacer esto, devolvería True
incluso si el valor del atributo fuera de tipo None
y esto causaría que mi comando set
ElementTree fallara.
hasattr(temp, ''hair'')
>>True
Si el valor del atributo era de tipo None
, getattr
también lo devolvería, lo que provocaría un error en mi comando set
ElementTree.
c = getattr(temp, ''hair'')
type(c)
>> NoneType
Utilizo el siguiente método para atender estos casos ahora:
def getRealAttr(class_obj, class_attr, default = ''''):
temp = getattr(class_obj, class_attr, default)
if temp is None:
temp = default
elif type(temp) != str:
temp = str(temp)
return temp
Esto es cuando y como uso getattr
.
Los objetos en Python pueden tener atributos (en realidad, cada objeto tiene atributos incorporados: atributos y métodos de datos (las funciones son valores, es decir, los objetos también) para trabajar con ellos).
Por ejemplo, usted tiene una person
objeto, que tiene varios atributos: name
, gender
, etc.
Se accede a estos atributos (ya sean métodos u objetos de datos) que normalmente se escriben: person.name
, person.gender
, person.the_method()
, etc.
Pero, ¿qué sucede si no conoce el nombre del atributo en el momento en que escribe el programa? Por ejemplo, tiene el nombre del atributo almacenado en una variable llamada attr_name
.
Si
attr_name = ''gender''
entonces, en lugar de escribir
gender = person.gender
puedes escribir
gender = getattr(person, attr_name)
Algo de practica
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
>>> class Person():
... name = ''Victor''
... def say(self, what):
... print(self.name, what)
...
>>> getattr(Person, ''name'')
''Victor''
>>> attr_name = ''name''
>>> person = Person()
>>> getattr(person, attr_name)
''Victor''
>>> getattr(person, ''say'')(''Hello'')
Victor Hello
getattr
elevará AttributeError
si el atributo con el nombre dado no existe en el objeto:
>>> getattr(person, ''age'')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ''Person'' object has no attribute ''age''
Pero puede pasar un valor predeterminado como el tercer argumento, que se devolverá si tal atributo no existe:
>>> getattr(person, ''age'', 0)
0
Puede usar getattr
junto con dir
para iterar sobre todos los nombres de atributos y obtener sus valores:
>>> dir(1000)
[''__abs__'', ''__add__'', ..., ''__trunc__'', ''__xor__'', ''bit_length'', ''conjugate'', ''denominator'', ''from_bytes'', ''imag'', ''numerator'', ''real'', ''to_bytes'']
>>> obj = 1000
>>> for attr_name in dir(obj):
... attr_value = getattr(obj, attr_name)
... print(attr_name, attr_value, callable(attr_value))
...
__abs__ <method-wrapper ''__abs__'' of int object at 0x7f4e927c2f90> True
...
bit_length <built-in method bit_length of int object at 0x7f4e927c2f90> True
...
>>> getattr(1000, ''bit_length'')()
10
Un uso práctico para esto sería encontrar todos los métodos cuyos nombres comienzan con la test
y llamarlos .
Similar a getattr
existe setattr
que le permite establecer un atributo de un objeto que tiene su nombre:
>>> setattr(person, ''name'', ''Andrew'')
>>> person.name # accessing instance attribute
''Andrew''
>>> Person.name # accessing class attribute
''Victor''
>>>
Otro uso de getattr () en la implementación de una instrucción switch en Python. Utiliza tanto la reflexión para obtener el tipo de caso.
import sys
class SwitchStatement(object):
""" a class to implement switch statement and a way to show how to use gettattr in Pythion"""
def case_1(self):
return "value for case_1"
def case_2(self):
return "value for case_2"
def case_3(self):
return "value for case_3"
def case_4(self):
return "value for case_4"
def case_value(self, case_type=1):
"""This is the main dispatchmethod, that uses gettattr"""
case_method = ''case_'' + str(case_type)
# fetch the relevant method name
# Get the method from ''self''. Default to a lambda.
method = getattr(self, case_method, lambda: "Invalid case type")
# Call the method as we return it
return method()
def main(_):
switch = SwitchStatement()
print swtich.case_value(_)
if __name__ == ''__main__'':
main(int(sys.argv[1]))
Para mí, getattr es más fácil de explicar de esta manera:
Le permite llamar a métodos basados en el contenido de una cadena en lugar de escribir el nombre del método.
Por ejemplo, no puedes hacer esto:
obj = MyObject()
for x in [''foo'', ''bar'']:
obj.x()
porque x no es del tipo "builtin", sino "str". Sin embargo, PUEDES hacer esto:
obj = MyObject()
for x in [''foo'', ''bar'']:
getattr(obj, x)()
Le permite conectarse dinámicamente con objetos basándose en su entrada. Lo he encontrado útil cuando se trata de objetos y módulos personalizados.
Puedes ver un ejemplo completo aquí:
La introspección se puede usar para diferentes propósitos, la que se presenta en ''Dive Into Python'' es simplemente una forma de agregar funcionalidad (plug-in) dinámicamente en su aplicación.
Me refiero dinámicamente sin hacer modificaciones en la aplicación central para agregar una nueva característica.
Tomando el ejemplo de ''Dive Into Python'', una aplicación sencilla para extraer el atributo de un archivo de otro archivo , puede agregar el manejo de un nuevo formato de archivo sin modificar la aplicación original.
Te recomiendo que termines el libro. Mientras lees, todo se volverá cada vez más claro.
Un caso de uso bastante común para getattr
es la asignación de datos a funciones.
Por ejemplo, en un marco web como Django o Pylons, getattr
facilita la getattr
la URL de una solicitud web a la función que la va a manejar. Si mira bajo el capó del enrutamiento de Pylons, por ejemplo, verá que (de manera predeterminada, al menos) corta la URL de una solicitud, como:
http://www.example.com/customers/list
en "clientes" y "lista". Luego busca una clase de controlador llamada CustomerController
. Suponiendo que encuentra la clase, crea una instancia de la clase y luego usa getattr
para obtener su método de list
. Luego llama a ese método, pasándole la solicitud como un argumento.
Una vez que capte esta idea, se vuelve realmente fácil ampliar la funcionalidad de una aplicación web: solo agregue nuevos métodos a las clases de controladores y luego cree enlaces en sus páginas que usen las URL adecuadas para esos métodos. Todo esto es posible por getattr
.
# getattr
class hithere():
def french(self):
print ''bonjour''
def english(self):
print ''hello''
def german(self):
print ''hallo''
def czech(self):
print ''ahoj''
def noidea(self):
print ''unknown language''
def dispatch(language):
try:
getattr(hithere(),language)()
except:
getattr(hithere(),''noidea'')()
# note, do better error handling than this
dispatch(''french'')
dispatch(''english'')
dispatch(''german'')
dispatch(''czech'')
dispatch(''spanish'')