Czym jest dokładnie getattr () i jak go używać?

295

Niedawno przeczytałem o tej getattr()funkcji . Problem polega na tym, że wciąż nie mogę pojąć jego użycia. Jedyne, co rozumiem, getattr()to getattr(li, "pop")to, że dzwonię li.pop.

Nie rozumiałem, kiedy książka wspomniała, jak go używać, aby uzyskać odniesienie do funkcji, nie znając jej nazwy do czasu wykonania. Może w ogóle to jestem noob w programowaniu. Czy ktoś mógłby rzucić nieco światła na ten temat? Kiedy i jak dokładnie tego używać?

Terence Ponce
źródło
Z którą częścią masz problem? Atrybuty jako ciągi znaków? Funkcje pierwszej klasy?
Ignacio Vazquez-Abrams
1
Myślę, że moim problemem jest zrozumienie pojęcia getattr (). Nadal nie rozumiem jego celu.
Terence Ponce,
@Terence czy moja odpowiedź nie wyjaśnia sprawy?
Alois Cochard,
@Alois, twoja odpowiedź zdecydowanie rozwiała niektóre moje wątpliwości, ale wciąż nie mogę w pełni zrozumieć, do czego służy getattr ().
Terence Ponce,
6
@ S.Lott, zrobiłem. Dokumentacja miała tylko definicję, więc byłem trochę zdezorientowany co do jej użycia. Rozumiem getattr teraz po przeczytaniu o nim więcej.
Terence Ponce,

Odpowiedzi:

87

getattr(object, 'x') jest całkowicie równoważne z object.x.

tylko dwa przypadki, w których getattrmoże być przydatne.

  • nie możesz pisać object.x, ponieważ nie wiesz z góry, który atrybut chcesz (pochodzi z ciągu). Bardzo przydatne do metaprogramowania.
  • chcesz podać wartość domyślną. object.ypodniesie i AttributeErrorjeśli nie ma y. Ale getattr(object, 'y', 5)wróci 5.
niebieska notatka
źródło
2
Myślę, że to powinna być zaakceptowana odpowiedź. Bardzo jasne i na temat.
yuqli
290

Obiekty w Pythonie mogą mieć atrybuty - atrybuty danych i funkcje do pracy z tymi (metodami). Właściwie każdy obiekt ma wbudowane atrybuty.

Na przykład masz obiekt person, który ma kilka atrybutów: name, gender, itd.

Dostęp do tych atrybutów (może to być metody lub obiektów danych) zwykle pisząc: person.name, person.gender,person.the_method() , itd.

Ale co, jeśli nie znasz nazwy atrybutu w momencie pisania programu? Na przykład masz nazwę atrybutu zapisaną w zmiennej o nazwie attr_name.

gdyby

attr_name = 'gender'

to zamiast pisać

gender = person.gender

Możesz pisać

gender = getattr(person, attr_name)

Niektóre praktyki:

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

getattrpodniesie się, AttributeErrorjeśli atrybut o podanej nazwie nie istnieje w obiekcie:

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

Ale możesz przekazać wartość domyślną jako trzeci argument, który zostanie zwrócony, jeśli taki atrybut nie istnieje:

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

Możesz użyć getattrwraz z, diraby iterować wszystkie nazwy atrybutów i uzyskać ich wartości:

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

Praktycznym zastosowaniem tego byłoby znalezienie wszystkich metod, których nazwy rozpoczynają się od testi wywoływania ich .

Podobnie jak getattrtam, setattrktóry pozwala ustawić atrybut obiektu o nazwie:

>>> setattr(person, 'name', 'Andrew')
>>> person.name  # accessing instance attribute
'Andrew'
>>> Person.name  # accessing class attribute
'Victor'
>>>
warvariuc
źródło
9
Wydaje mi się, że getattr(..)należy tego użyć w 2 scenariuszach: 1. kiedy nazwa atrybutu jest wartością wewnątrz zmiennej (np. getattr(person, some_attr)) I 2. kiedy musimy użyć trzeciego argumentu pozycyjnego dla wartości domyślnej (np getattr(person, 'age', 24).). Jeśli widzę taki scenariuszgetattr(person, 'age') , wydaje mi się, że jest identyczny, person.ageco prowadzi mnie do myślenia, że person.agejest bardziej pythoniczny. Czy to jest poprawne?
wpcarro
102

Dla mnie, getattr najłatwiej to wytłumaczyć w ten sposób:

Pozwala na wywoływanie metod na podstawie zawartości ciągu zamiast wpisywania nazwy metody.

Na przykład nie możesz tego zrobić:

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

ponieważ x nie jest tego typu builtin, ale str. Możesz jednak to zrobić:

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

Pozwala dynamicznie łączyć się z obiektami na podstawie wprowadzonych danych. Uznałem, że jest to przydatne w przypadku niestandardowych obiektów i modułów.

NuclearPeon
źródło
2
To dość prosta i precyzyjna odpowiedź.
user6037143
43

Dość powszechny przypadek użycia dla getattr jest mapowanie danych do funkcji.

Na przykład w frameworku internetowym, takim jak Django lub Pylons, getattrułatwia mapowanie adresu URL żądania internetowego na funkcję, która będzie go obsługiwać. Jeśli na przykład spojrzysz pod maską routingu Pylons, zobaczysz, że (przynajmniej domyślnie) tnie adres URL żądania, na przykład:

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

na „klientów” i „listę”. Następnie szuka klasy kontrolera o nazwie CustomerController. Zakładając, że znajdzie klasę, tworzy instancję klasy, a następnie używa getattrjej do uzyskanialist metody. Następnie wywołuje tę metodę, przekazując jej żądanie jako argument.

Po zrozumieniu tego pomysłu bardzo łatwo jest rozszerzyć funkcjonalność aplikacji internetowej: po prostu dodaj nowe metody do klas kontrolerów, a następnie utwórz linki na swoich stronach, które używają odpowiednich adresów URL dla tych metod. Wszystko to jest możliwe dzięki getattr.

Robert Rossney
źródło
13

Oto szybki i nieprzyzwoity przykład tego, w jaki sposób klasa może uruchamiać różne wersje metody składowania w zależności od używanego systemu operacyjnego 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

Teraz użyjmy tej klasy w przykładzie:

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
źródło
7

Oprócz wszystkich niesamowitych odpowiedzi tutaj jest sposób na użycie getattr na zapisanie dużej liczby wierszy kodu i utrzymanie go w dobrym stanie. Ta myśl pojawiła się w następstwie okropnego przedstawienia kodu, co czasami może być koniecznością.

Scenariusz

Załóżmy, że twoja struktura katalogów jest następująca:

- superheroes.py
- properties.py

I masz funkcje do uzyskiwania informacji na temat Thor, Iron Man, Doctor Strangew superheroes.py. Bardzo mądrze zapisujesz właściwości wszystkich z nich properties.pyw formie kompaktowej, dicta następnie uzyskujesz do nich dostęp.

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

Powiedzmy, że chcesz zwrócić możliwości każdego z nich na żądanie superheroes.py. Istnieją więc funkcje takie jak

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

... i więcej funkcji zwracających różne wartości na podstawie klawiszy i superbohatera.

Z pomocą getattrmożesz zrobić coś takiego:

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

Znacząco zmniejszyłeś liczbę wierszy kodu, funkcji i powtórzeń!

No i oczywiście, jeśli masz złe nazwy, takie jak properties_of_thorzmienne, można je utworzyć i uzyskać do nich dostęp po prostu przez zrobienie

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

UWAGA: W przypadku tego konkretnego problemu mogą istnieć mądrzejsze sposoby radzenia sobie z sytuacją, ale chodzi o to, aby dać wgląd getattrw korzystanie z odpowiednich miejsc do pisania czystszego kodu.

unixia
źródło
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
źródło
2
Czy mógłbyś uściślić swoją odpowiedź, dodając nieco więcej informacji na temat oferowanego rozwiązania?
abarisone
3

Czasami getattr(..)leniwie inicjuję atrybuty o drugorzędnym znaczeniu tuż przed ich użyciem w kodzie.

Porównaj następujące:

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

Do tego:

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

Zaletą drugiego sposobu jest to, że n_calls_to_plotpojawia się tylko wokół miejsca w kodzie, w którym jest używany. Jest to dobre z punktu widzenia czytelności, ponieważ (1) możesz od razu zobaczyć, od jakiej wartości zaczyna się czytając, w jaki sposób jest on używany, (2) nie wprowadza rozproszenia w __init__(..)metodzie, co idealnie powinno dotyczyć stanu koncepcyjnego klasy , zamiast jakiegoś licznika narzędzi, który jest używany tylko przez jedną z metod funkcji z przyczyn technicznych, takich jak optymalizacja, i nie ma nic wspólnego ze znaczeniem obiektu.

Jewgienij Siergiejew
źródło
3

Dość często, gdy tworzę plik XML z danych przechowywanych w klasie, często otrzymuję błędy, jeśli atrybut nie istnieje lub jest typu None. W tym przypadku mój problem nie polegał na nieznajomości nazwy atrybutu, jak podano w pytaniu, ale raczej na danych przechowywanych w tym atrybucie.

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

Gdybym hasattrto robił, zwróciłoby Trueto wartość, nawet jeśli wartość atrybutu była typu, Nonea to spowodowałoby niepowodzenie mojej setkomendy ElementTree .

hasattr(temp, 'hair')
>>True

Jeśli wartość atrybutu byłaby typu None, getattrzwróciłaby ją również, co spowodowałoby niepowodzenie mojej setkomendy ElementTree .

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

Korzystam z następującej metody, aby zająć się teraz tymi sprawami:

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

To jest kiedy i jak używam getattr.

btathalon
źródło
3

Kolejne zastosowanie getattr () w implementacji instrukcji switch w Pythonie. Używa obu odbić, aby uzyskać typ sprawy.

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
źródło
Podoba mi się ta odpowiedź, ale popraw małe literówki
maja
2

setattr ()

Używamy setattr, aby dodać atrybut do naszej instancji klasy. Przekazujemy instancję klasy, nazwę atrybutu i wartość.

getattr ()

W getattr odzyskujemy te wartości

Na przykład

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
źródło
1

Myślę, że ten przykład jest oczywisty. Działa metoda pierwszego parametru, którego nazwa jest podana w drugim parametrze.

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
źródło
0

Wyjaśnia to również 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)

Wiek to: 23 lata

Wiek to: 23 lata

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'))

Płeć to: mężczyzna

AttributeError: Obiekt „Osoba” nie ma atrybutu „seks”

Barny
źródło
0

Próbowałem w Python2.7.17

Niektórzy inni już odpowiedzieli. Jednak próbowałem wywołać getattr (obj, 'set_value') i to nie wykonało metody set_value, więc zmieniłem na getattr (obj, 'set_value') () -> To pomaga wywołać to samo.

Przykładowy kod:

Przykład 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
źródło