O que é getattr () exatamente e como eu o uso?

295

Eu ligetattr() recentemente sobre a função . O problema é que ainda não consigo entender a ideia de seu uso. A única coisa que entendo getattr()é que getattr(li, "pop")é o mesmo que ligar li.pop.

Não entendi quando o livro mencionou como você o usa para obter uma referência a uma função sem saber seu nome até o tempo de execução. Talvez este seja eu sendo um noob em programação, em geral. Alguém poderia lançar alguma luz sobre o assunto? Quando e como uso exatamente isso?

Terence Ponce
fonte
Com qual parte você está tendo problemas? Atributos como strings? Funções de primeira classe?
Ignacio Vazquez-Abrams
1
Eu acho que meu problema é entender o conceito de getattr (). Eu ainda não entendo seu propósito.
Terence Ponce
@Terence minha resposta não torna as coisas mais claras?
Alois Cochard
@Alois, sua resposta definitivamente esclareceu algumas das minhas dúvidas, mas ainda não consigo entender completamente para que serve getattr ().
Terence Ponce
6
@ S.Lott, eu fiz. A documentação tinha apenas a definição, então fiquei meio confuso sobre o uso. Eu entendo getattr agora depois de ler mais sobre isso embora.
Terence Ponce

Respostas:

88

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

Existem apenas dois casos em que getattrpodem ser úteis.

  • você não pode escrever object.x, porque não sabe com antecedência qual atributo deseja (vem de uma string). Muito útil para meta-programação.
  • você deseja fornecer um valor padrão. object.yaumentará um AttributeErrorse não houver y. Mas getattr(object, 'y', 5)vai voltar 5.
nota azul
fonte
2
Eu sinto que essa deve ser a resposta aceita. Muito claro e direto ao ponto.
yuqli 31/07/19
290

Objetos no Python podem ter atributos - atributos e funções de dados para trabalhar com esses (métodos). Na verdade, todo objeto possui atributos internos.

Por exemplo, você tem um objeto person, que tem vários atributos: name, gender, etc.

Você acessa esses atributos (seja métodos ou objetos de dados) normalmente escrever: person.name, person.gender, person.the_method(), etc.

Mas e se você não souber o nome do atributo no momento em que escreve o programa? Por exemplo, você tem o nome do atributo armazenado em uma variável chamada attr_name.

E se

attr_name = 'gender'

então, ao invés de escrever

gender = person.gender

você pode escrever

gender = getattr(person, attr_name)

Alguma prática:

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

getattraumentará AttributeErrorse o atributo com o nome fornecido não existir no objeto:

>>> getattr(person, 'age')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'age'

Mas você pode passar um valor padrão como o terceiro argumento, que será retornado se esse atributo não existir:

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

Você pode usar getattrjunto com dirpara iterar todos os nomes de atributos e obter seus 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

Um uso prático para isso seria encontrar todos os métodos cujos nomes começam teste os chamam .

Semelhante ao getattrque há setattrque lhe permite definir um atributo de um objeto que tem o seu nome:

>>> setattr(person, 'name', 'Andrew')
>>> person.name  # accessing instance attribute
'Andrew'
>>> Person.name  # accessing class attribute
'Victor'
>>>
warvariuc
fonte
9
Parece-me, portanto, que getattr(..)deve ser usado em 2 cenários: 1. quando o nome do atributo é um valor dentro de uma variável (por exemplo getattr(person, some_attr)) e 2. quando precisamos usar o terceiro argumento posicional para o valor padrão (por exemplo getattr(person, 'age', 24)). Se vejo um cenário como getattr(person, 'age')esse, parece-me idêntico ao person.ageque me leva a pensar que person.ageé mais pitônico. Isso está correto?
Wpcarro
102

Para mim, getattré mais fácil explicar dessa maneira:

Ele permite que você chame métodos com base no conteúdo de uma string em vez de digitar o nome do método.

Por exemplo, você não pode fazer isso:

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

porque x não é do tipo builtin, mas str. No entanto, você pode fazer isso:

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

Ele permite que você se conecte dinamicamente com objetos com base em sua entrada. Eu achei útil ao lidar com objetos e módulos personalizados.

NuclearPeon
fonte
2
Esta é uma resposta bastante direta e precisa.
user6037143
43

Um caso de uso bastante comum getattré o mapeamento de dados para funções.

Por exemplo, em uma estrutura da web como Django ou Pylons, getattré fácil mapear o URL de uma solicitação da web para a função que irá lidar com isso. Se você olhar sob o capô do roteamento do Pylons, por exemplo, verá que (por padrão, pelo menos) ele divide o URL de uma solicitação, como:

http://www.example.com/customers/list

em "clientes" e "lista". Em seguida, ele procura uma classe de controlador chamada CustomerController. Supondo que encontre a classe, ele cria uma instância da classe e usa getattrpara obter seu listmétodo. Em seguida, chama esse método, passando a solicitação como argumento.

Depois de entender essa idéia, fica muito fácil estender a funcionalidade de um aplicativo Web: basta adicionar novos métodos às classes do controlador e criar links nas suas páginas que usem os URLs apropriados para esses métodos. Tudo isso é possível graças a getattr.

Robert Rossney
fonte
13

Aqui está um exemplo rápido e sujo de como uma classe pode disparar versões diferentes de um método save, dependendo do sistema operacional que está sendo executado 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

Agora vamos usar esta classe em um exemplo:

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
fonte
7

Além de todas as respostas surpreendentes aqui, há uma maneira de usar getattrpara salvar linhas de código copiosas e mantê-las confortáveis. Esse pensamento veio após a terrível representação do código que às vezes pode ser uma necessidade.

Cenário

Suponha que sua estrutura de diretórios seja a seguinte:

- superheroes.py
- properties.py

E, você tem funções para obter informações sobre Thor, Iron Man, Doctor Strangeem superheroes.py. Você muito inteligente anota as propriedades de todas elas de properties.pyforma compacta dicte as acessa.

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'],
}

Agora, digamos que você queira retornar os recursos de cada um deles sob demanda superheroes.py. Então, existem funções 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']

... e mais funções retornando valores diferentes com base nas chaves e no super-herói.

Com a ajuda de getattr, você pode fazer 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']

Você reduziu consideravelmente o número de linhas de código, funções e repetição!

Ah, é claro, se você tem nomes ruins como properties_of_thorpara variáveis, eles podem ser criados e acessados ​​simplesmente fazendo

def get_superhero_weapon(hero):
    superhero = 'properties_of_{}'.format(hero)
    all_properties = getattr(properties, superhero)
    return all_properties['weapon']

NOTA: Para esse problema em particular, pode haver maneiras mais inteligentes de lidar com a situação, mas a idéia é fornecer uma visão sobre o uso getattrnos locais certos para escrever um código mais limpo.

unixia
fonte
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
fonte
2
Você poderia elaborar mais sua resposta adicionando um pouco mais de descrição sobre a solução que você fornece?
26415 abarisone
3

Às vezes eu uso getattr(..)para inicializar preguiçosamente atributos de importância secundária pouco antes de serem usados ​​no código.

Compare o seguinte:

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

Para isso:

class Graph(object):
    def plot(self):
        self.n_calls_to_plot = 1 + getattr(self, "n_calls_to_plot", 0)

A vantagem da segunda maneira é que n_calls_to_plotapenas aparece em torno do local no código em que é usado. Isso é bom para facilitar a leitura, porque (1) você pode ver imediatamente com que valor ele começa quando lê como é usado; (2) não introduz uma distração no __init__(..)método, o que idealmente deveria ser sobre o estado conceitual da classe. , em vez de algum contador de utilidade que é usado apenas por um dos métodos da função por razões técnicas, como otimização, e não tem nada a ver com o significado do objeto.

Evgeni Sergeev
fonte
3

Com bastante frequência, ao criar um arquivo XML a partir de dados armazenados em uma classe, eu frequentemente recebia erros se o atributo não existisse ou fosse do tipo None. Nesse caso, meu problema não era saber qual era o nome do atributo, conforme declarado na sua pergunta, mas sim os dados já armazenados nesse atributo.

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

Se eu hasattrfizesse isso, ele retornaria Truemesmo que o valor do atributo fosse do tipo Nonee isso causaria setfalha no meu comando ElementTree .

hasattr(temp, 'hair')
>>True

Se o valor do atributo fosse do tipo None, getattrtambém o retornaria, o que causaria setfalha no meu comando ElementTree .

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

Eu uso o seguinte método para cuidar desses casos agora:

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

É quando e como eu uso getattr.

btathalon
fonte
3

Outro uso do getattr () na implementação de uma instrução switch no Python. Ele usa a reflexão para obter o 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
fonte
Eu gosto desta resposta, mas por favor, corrigir os pequenos erros de digitação
podem
2

setattr ()

Usamos setattr para adicionar um atributo à nossa instância de classe. Passamos a instância da classe, o nome do atributo e o valor.

getattr ()

Com getattr , recuperamos esses valores

Por exemplo

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
fonte
1

Eu acho que este exemplo é auto-explicativo. Ele executa o método do primeiro parâmetro, cujo nome é fornecido no 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
fonte
0

Também está esclarecendo em 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)

A idade é: 23

A idade é: 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'))

O sexo é: Masculino

AttributeError: o objeto 'Person' não tem atributo 'sex'

Barny
fonte
0

Eu tentei no Python2.7.17

Alguns dos colegas já responderam. No entanto, tentei chamar getattr (obj, 'set_value') e isso não executou o método set_value, então mudei para getattr (obj, 'set_value') () -> Isso ajuda a invocar o mesmo.

Código de exemplo:

Exemplo 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
fonte