Python getattr() and setattr() Nested

4/5 - (2 votes)

Understanding Python’s setattr() and getattr() Functions

Next, you’ll learn about the “normal”, non-nested and non-recursive get and set attribute functions. If you already know them well, there’s no need to read this section and you can skip ahead right to the problem formulation and solution.

Let’s start with the setattr() function, followed by getattr().

setattr()

Python’s built-in setattr(object, string, value) function takes three arguments:

  • an object,
  • a string, and
  • an arbitrary value.

It sets the attribute given by the string on the object to the specified value.

After calling the function, there’s a new or updated attribute at the given instance, named and valued as provided in the arguments.

For example, setattr(object, 'attribute', 42) is equivalent to object.attribute = 42.

Python setattr() - The Ultimate Guide

🌍 Recommended Tutorial: Python setattr() Built-in Function

getattr()

Python’s built-in getattr(object, string) function returns the value of the object‘s attribute with name string.

If this doesn’t exist, it returns the value provided as an optional third default argument.

If that doesn’t exist either, it raises an AttributeError.

An example is getattr(porsche, 'speed') which is equivalent to porsche.speed.

Python getattr() - Ultimate Guide

🌍 Recommended Tutorial: Python getattr() Built-in Function

Problem Formulation

Consider the following example that creates four classes Family, Mom, Dad, and Kid. The class Family points to the other three nested classes (as attributes). The highlighted lines show how you try to set the attribute of a nested class within the Family class fam using the setattr() function.

class Family(object):
    def __init__(self):
        self.mom = Mom()
        self.dad = Dad()
        self.kid = Kid('Ann')

class Mom(object):
    def __init__(self, name='Jane'):
        self.name = 'Jane'

class Dad(object):
    def __init__(self, name='Jane'):
        self.name = 'John'

class Kid(object):
    def __init__(self, name):
        self.name = name

fam = Family()
setattr(fam, 'mom.age', 33)
setattr(fam, 'kid.sport', 'soccer')
print(fam.__dict__)


So, you try to create an attribute age and an attribute sport for the first kid. However, it doesn’t work—Python simply creates a weirdly-named attribute mom.age and kid.sport on the Family class.

{'mom': <__main__.Mom object at 0x0000020BB73BE9A0>, 
 'dad': <__main__.Dad object at 0x0000020BB73BE910>,
 'kids': [<__main__.Kid object at 0x0000020BB73BE040>, 
          <__main__.Kid object at 0x0000020BB73BE1C0>], 
 'mom.age': 33, 
 'kid.sport': 'soccer'}

Solution

We use the excellent solution by unutbu that creates two recursive functions rsetattr() and rgetattr() that essentially call themselves recursively to obtain the desired outcome of setting attributes of nested classes.

See the highlighted lines:

import functools


def rsetattr(obj, attr, val):
    pre, _, post = attr.rpartition('.')
    return setattr(rgetattr(obj, pre) if pre else obj, post, val)

def rgetattr(obj, attr, *args):
    def _getattr(obj, attr):
        return getattr(obj, attr, *args)
    return functools.reduce(_getattr, [obj] + attr.split('.'))


#####

class Family(object):
    def __init__(self):
        self.mom = Mom()
        self.dad = Dad()
        self.kid = Kid('Ann')

class Mom(object):
    def __init__(self, name='Jane'):
        self.name = 'Jane'

class Dad(object):
    def __init__(self, name='Jane'):
        self.name = 'John'

class Kid(object):
    def __init__(self, name):
        self.name = name

fam = Family()
rsetattr(fam, 'mom.age', 33)
rsetattr(fam, 'kid.sport', 'soccer')
print(fam.__dict__)
# {'mom': <__main__.Mom object at 0x000002601DE408E0>, 'dad': <__main__.Dad object at 0x000002601DE40190>, 'kid': <__main__.Kid object at 0x000002601DE40250>}


print(fam.mom.age)
# 33

print(fam.kid.sport)
# soccer


Note that the second part of the code after the definition of the rgetattr() and rsetattr() functions remains pretty much unchanged.

Unlike the previous example, however, using 'mom.age' and 'kid.sport' as name arguments of rsetattr() actually do set an attribute at a nested subclass mom or kid, instead of a flat attribute named 'mom.age' or 'kid.sport' with the dot character '.' as part of the attribute name.

You can see that the recursion even goes deeper than that — if you need it to go that far:

rsetattr(fam, 'mom.fam', Family())
rsetattr(fam, 'mom.fam.mom.car', 'porsche')
print(fam.mom.fam.mom.car)
# porsche

Where to Go From Here?

Thanks for reading this tutorial and for taking time out of your day to learn with Finxter.

If you want to keep improving your skills, feel free to join our free email academy and download our Python cheat sheets — they are free!