What is getattr() exactly and how do I use it?

2018-12-31 23:25发布

问题:

I was reading about the getattr() function. The problem is that I still can\'t grasp the idea of its usage. The only thing I understand about getattr() is that getattr(li, \"pop\") is the same as calling li.pop.

I didn\'t understand when the book mentioned how you use it to get a reference to a function without knowing its name until run-time. Maybe this is me being a noob in programming, in general. Could anyone shed some light to the subject? When and how do I use this exactly?

回答1:

You can view a full example here:

  • https://linux.die.net/diveintopython/html/power_of_introspection/index.html

Introspection can be used for different purpose, the one presented in \'Dive Into Python\' is simply a way to add functionality (plug-in) dynamically in your application.

By dynamically I mean without doing modification in the core application to add a new feature.

Taking the \'Dive Into Python\' example - a simple app to extract attribute from file of different file - you can add the handling of a new file format without doing modification to the original application.

I recommend that you finish the book. As you read, everything will become increasingly clear.



回答2:

Objects in Python can have attributes -- data attributes and functions to work with those (methods). Actually, every object has built-in attributes.

For example you have an object person, that has several attributes: name, gender, etc.

You access these attributes (be it methods or data objects) usually writing: person.name, person.gender, person.the_method(), etc.

But what if you don\'t know the attribute\'s name at the time you write the program? For example you have attribute\'s name stored in a variable called attr_name.

if

attr_name = \'gender\'

then, instead of writing

gender = person.gender

you can write

gender = getattr(person, attr_name)

Some practice:

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 will raise AttributeError if attribute with the given name does not exist in the object:

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

But you can pass a default value as the third argument, which will be returned if such attribute does not exist:

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

You can use getattr along with dir to iterate over all attribute names and get their values:

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

A practical use for this would be to find all methods whose names start with test and call them.

Similar to getattr there is setattr which allows you to set an attribute of an object having its name:

>>> setattr(person, \'name\', \'Andrew\')
>>> person.name  # accessing instance attribute
\'Andrew\'
>>> Person.name  # accessing class attribute
\'Victor\'
>>>


回答3:

For me, getattr is easiest to explain this way:

It allows you to call methods based on the contents of a string instead of typing the method name.

For example, you cannot do this:

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

because x is not of the type \"builtin\", but \"str\". However, you CAN do this:

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

It allows you to dynamically connect with objects based on your input. I\'ve found it useful when dealing with custom objects and modules.



回答4:

A pretty common use case for getattr is mapping data to functions.

For instance, in a web framework like Django or Pylons, getattr makes it straightforward to map a web request\'s URL to the function that\'s going to handle it. If you look under the hood of Pylons\'s routing, for instance, you\'ll see that (by default, at least) it chops up a request\'s URL, like:

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

into \"customers\" and \"list\". Then it searches for a controller class named CustomerController. Assuming it finds the class, it creates an instance of the class and then uses getattr to get its list method. It then calls that method, passing it the request as an argument.

Once you grasp this idea, it becomes really easy to extend the functionality of a web application: just add new methods to the controller classes, and then create links in your pages that use the appropriate URLs for those methods. All of this is made possible by getattr.



回答5:

Here\'s a quick and dirty example of how a class could fire different versions of a save method depending on which operating system it\'s being executed on using 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

Now let\'s use this class in an example:

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


回答6:

Other than all the amazing answers here, there is a way to use getattr to save copious lines of code and keeping it snug. This thought came following the dreadful representation of code that sometimes might be a necessity.

Scenario

Suppose your directory structure is as follows:

- superheroes.py
- properties.py

And, you have functions for getting information about Thor, Iron Man, Doctor Strange in superheroes.py. You very smartly write down the properties of all of them in properties.py in a compact dict and then access them.

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

Now, let\'s say you want to return capabilities of each of them on demand in superheroes.py. So, there are functions like

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

...and more functions returning different values based on the keys and superhero.

With the help of getattr, you could do something like:

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

You considerably reduced the number of lines of code, functions and repetition!

Oh and of course, if you have bad names like properties_of_thor for variables , they can be made and accessed by simply doing

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

NOTE: For this particular problem, there can be smarter ways to deal with the situation, but the idea is to give an insight about using getattr in right places to write cleaner code.



回答7:

I sometimes use getattr(..) to lazily initialise attributes of secondary importance just before they are used in the code.

Compare the following:

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

To this:

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

The advantage of the second way is that n_calls_to_plot only appears around the place in the code where it is used. This is good for readability, because (1) you can immediately see what value it starts with when reading how it\'s used, (2) it doesn\'t introduce a distraction into the __init__(..) method, which ideally should be about the conceptual state of the class, rather than some utility counter that is only used by one of the function\'s methods for technical reasons, such as optimisation, and has nothing to do with the meaning of the object.



回答8:

Quite frequently when I am creating an XML file from data stored in a class I would frequently receive errors if the attribute didn\'t exist or was of type None. In this case, my issue wasn\'t not knowing what the attribute name was, as stated in your question, but rather was data ever stored in that attribute.

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

If I used hasattr to do this, it would return True even if the attribute value was of type None and this would cause my ElementTree set command to fail.

hasattr(temp, \'hair\')
>>True

If the attribute value was of type None, getattr would also return it which would cause my ElementTree set command to fail.

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

I use the following method to take care of these cases now:

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

This is when and how I use getattr.



回答9:

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


回答10:

Another use of getattr() in implementing a switch statement in Python. It uses both reflection to get the case type.

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