¿Qué es getattr () exactamente y cómo lo uso?

295

Recientemente leí sobre la getattr()función . El problema es que todavía no puedo entender la idea de su uso. Lo único que entiendo getattr()es que getattr(li, "pop")es lo mismo que llamar 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 un novato en programación, en general. ¿Alguien podría arrojar algo de luz sobre el tema? ¿Cuándo y cómo uso esto exactamente?

Terence Ponce
fuente
¿Con qué parte tienes problemas? Atributos como cadenas? Funciones de primera clase?
Ignacio Vázquez-Abrams
1
Creo que mi problema es entender el concepto de getattr (). Todavía no entiendo su propósito.
Terence Ponce
@Terence ¿mi respuesta no aclara las cosas?
Alois Cochard
@Alois, tu respuesta definitivamente despejó algunas de mis dudas, pero aún no puedo entender completamente para qué sirve getattr ().
Terence Ponce
66
@ S.Lott, lo hice. La documentación solo tenía la definición, así que estaba un poco confundido sobre su uso. Sin embargo, ahora entiendo getattr después de leer más al respecto.
Terence Ponce

Respuestas:

88

getattr(object, 'x') es completamente equivalente a object.x.

Solo hay dos casos en los que getattrpuede ser útil.

  • no puede escribir object.x, porque no sabe de antemano qué atributo desea (proviene de una cadena). Muy útil para la metaprogramación.
  • desea proporcionar un valor predeterminado. object.ylevantará un AttributeErrorsi no hay y. Pero getattr(object, 'y', 5)volveremos 5.
nota azul
fuente
2
Siento que esta debería ser la respuesta aceptada. Muy claro y al grano.
yuqli
290

Los objetos en Python pueden tener atributos: atributos de datos y funciones para trabajar con esos (métodos). En realidad, cada objeto tiene atributos incorporados.

Por ejemplo, usted tiene un objeto person, que tiene varios atributos: name, gender, etc.

Se accede a estos atributos (ya sea métodos u objetos de datos) suele escribir: person.name, person.gender, person.the_method(), etc.

Pero, ¿qué sucede si no conoce el nombre del atributo al momento de escribir 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)

Alguna práctica:

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

getattrse elevará AttributeErrorsi 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 tercer argumento, que se devolverá si dicho atributo no existe:

>>> getattr(person, 'age', 0)
0

Puede usar getattrjunto con diriterar 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 comiencen con ellostest y llamarlos .

Similar a getattrthere is, setattrque 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'
>>>
warvariuc
fuente
99
Por lo tanto, me parece que getattr(..)debería usarse en 2 escenarios: 1. cuando el nombre del atributo es un valor dentro de una variable (por ejemplo getattr(person, some_attr)) y 2. cuando necesitamos usar el tercer argumento posicional para el valor predeterminado (por ejemplo getattr(person, 'age', 24)). Si veo un escenario como getattr(person, 'age')este me parece que es idéntico al person.ageque me lleva a pensar que person.agees más Pitónico. ¿Es eso correcto?
wpcarro
102

Para mí, getattres más fácil explicarlo 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, pero str. Sin embargo, PUEDES hacer esto:

obj = MyObject()
for x in ['foo', 'bar']:
    getattr(obj, x)()

Le permite conectarse dinámicamente con objetos en función de su entrada. Lo he encontrado útil cuando se trata de objetos y módulos personalizados.

NuclearPeon
fuente
2
Esta es una respuesta bastante directa y precisa.
user6037143
43

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, getattres sencillo asignar la URL de una solicitud web a la función que la va a manejar. Si observa bajo el capó de la ruta de Pylons, por ejemplo, verá que (por defecto, 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 getattrpara obtener sulist método. Luego llama a ese método y le pasa la solicitud como argumento.

Una vez que comprenda esta idea, se vuelve realmente fácil ampliar la funcionalidad de una aplicación web: simplemente agregue nuevos métodos a las clases de controlador y luego cree enlaces en sus páginas que usen las URL apropiadas para esos métodos. Todo esto es posible gracias a getattr.

Robert Rossney
fuente
13

Aquí hay un ejemplo rápido y sucio de cómo una clase podría disparar diferentes versiones de un método de guardar dependiendo del sistema operativo en el que se ejecute 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()
Josh
fuente
7

Además de todas las respuestas sorprendentes aquí, hay una forma de usar getattrpara guardar copiosas líneas de código y mantenerlo ajustado. Este pensamiento surgió después de la terrible representación del código que a veces podría ser una necesidad.

Guión

Supongamos que la estructura de su directorio es la siguiente:

- superheroes.py
- properties.py

Y, que tiene funciones para obtener información acerca de Thor, Iron Man, Doctor Strangeen superheroes.py. Usted escribe muy inteligentemente las propiedades de todos ellos properties.pyen un compacto dicty 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, supongamos que desea devolver las capacidades de cada uno de ellos a pedido superheroes.py. Entonces, 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 valores diferentes según las teclas y el superhéroe.

Con la ayuda de getattr, podría 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']

¡Redujo considerablemente el número de líneas de código, funciones y repetición!

Ah, y por supuesto, si tiene nombres malos como properties_of_thorvariables, se pueden hacer y acceder 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 getattren los lugares correctos para escribir código más limpio.

unixia
fuente
3
# 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')
Kduyehj
fuente
2
¿Podría elaborar más su respuesta agregando un poco más de descripción sobre la solución que proporciona?
abarisone
3

A veces uso getattr(..) para inicializar perezosamente atributos de importancia secundaria justo antes de que se usen en el código.

Compare 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_plotsolo 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 cuando lee cómo se usa, (2) no introduce una distracción en el __init__(..)método, que idealmente debería ser sobre el estado conceptual de la clase , en lugar de algún contador de utilidad que solo es utilizado 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.

Evgeni Sergeev
fuente
3

Con mucha frecuencia cuando estoy creando un archivo XML a partir de datos almacenados en una clase, frecuentemente recibo errores si el atributo no existe o es de tipo None. En este caso, mi problema no era no saber cuál era el nombre del atributo, como se indicó en su pregunta, sino que los datos se almacenaban en ese atributo.

class Pet:
    def __init__(self):
        self.hair = None
        self.color = None

Si solía hasattrhacer esto, volvería Trueincluso si el valor del atributo fuera de tipo Noney esto haría que mi setcomando ElementTree fallara.

hasattr(temp, 'hair')
>>True

Si el valor del atributo fuera de tipo None, getattrtambién lo devolvería, lo que provocaría que mi setcomando ElementTree fallara.

c = getattr(temp, 'hair')
type(c)
>> NoneType

Ahora uso el siguiente método para atender estos casos:

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 cuándo y cómo uso getattr.

btathalon
fuente
3

Otro uso de getattr () en la implementación de una declaración de cambio en Python. Utiliza ambos reflejos 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]))
Jules Damji
fuente
Me gusta esta respuesta, pero arregla los pequeños errores tipográficos
mayo
2

setattr ()

Usamos setattr para agregar un atributo a nuestra instancia de clase. Pasamos la instancia de clase, el nombre del atributo y el valor.

getattr ()

Con getattr recuperamos estos valores

Por ejemplo

Employee = type("Employee", (object,), dict())

employee = Employee()

# Set salary to 1000
setattr(employee,"salary", 1000 )

# Get the Salary
value = getattr(employee, "salary")

print(value)
kuldeep Mishra
fuente
1

Creo que este ejemplo se explica por sí mismo. Ejecuta el método del primer parámetro, cuyo nombre se da en el segundo parámetro.

class MyClass:
   def __init__(self):
      pass
   def MyMethod(self):
      print("Method ran")

# Create an object
object = MyClass()
# Get all the methods of a class
method_list = [func for func in dir(MyClass) if callable(getattr(MyClass, func))]
# You can use any of the methods in method_list
# "MyMethod" is the one we want to use right now

# This is the same as running "object.MyMethod()"
getattr(object,'MyMethod')()
Dersu Giritlioğlu
fuente
0

También está aclarando desde https://www.programiz.com/python-programming/methods/built-in/getattr

class Person:
    age = 23
    name = "Adam"

person = Person()
print('The age is:', getattr(person, "age"))
print('The age is:', person.age)

La edad es: 23

La edad es: 23

class Person:
    age = 23
    name = "Adam"

person = Person()

# when default value is provided
print('The sex is:', getattr(person, 'sex', 'Male'))

# when no default value is provided
print('The sex is:', getattr(person, 'sex'))

El sexo es: masculino

AttributeError: el objeto 'Persona' no tiene atributo 'sexo'

Barny
fuente
0

Lo he intentado en Python2.7.17

Algunos de los compañeros ya respondieron. Sin embargo, intenté llamar a getattr (obj, 'set_value') y esto no ejecutó el método set_value, así que cambié a getattr (obj, 'set_value') () -> Esto ayuda a invocar lo mismo.

Código de ejemplo:

Ejemplo 1:

    class GETATT_VERIFY():
       name = "siva"
       def __init__(self):
           print "Ok"
       def set_value(self):
           self.value = "myself"
           print "oooh"
    obj = GETATT_VERIFY()
    print getattr(GETATT_VERIFY, 'name')
    getattr(obj, 'set_value')()
    print obj.value
siva balan
fuente