Python's setattr function

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
5 minute read Python 3.7—3.10

The setattr function is one of the lesser used built-in functions in Python.

Let's talk about Python's built-in setattr function.

Need to dynamically set an attribute?

We'd like to make a class that works like this:

>>> row = Row(id=4, name="duck", action="quack", color="purple")

Our Row class is supposed to accept any number of keyword arguments and assign each of them to an attribute on a new Row object.

We can use Python's ** operator to capture arbitrary keyword arguments into a dictionary:

class Row:
    def __init__(self, **attributes):
        for attribute, value in attributes.items():
            ...  # What should we do now?

But we need some way to store each item on our Row object as a new attribute.

Normally attribute assignments use an = sign with the . notation:

>>> row.color = "purple"

But we can't use the . notation because our attribute names are stored in strings. For example the variable attribute might contain the string "color" (representing the color attribute we're meant to assign to):

>>> attribute = "color"
>>> value = "purple"

And if we try using Python's usual attribute assignment notation:

>>> row.attribute = value

We'll end up with an attribute called attribute instead of an attribute called color:

>>> row.attribute

We need some way to dynamically assign an attribute!

Python's built-in setattr function to the rescue

Python's setattr function accepts an object (the object we're adding the attribute to), a string representing an attribute name, and a value to assign.

>>> help(setattr)
Help on built-in function setattr in module builtins:

setattr(obj, name, value, /)
    Sets the named attribute on the given object to the specified value.

    setattr(x, 'y', v) is equivalent to ``x.y = v''

So calling the setattr function like this:

>>> setattr(row, "color", "purple")

Is equivalent to assigning an attribute like this:

>>> row.color = "purple"

We could use this to implement that Row class we've been writing! We can use setattr for each attribute we need to assign:

class Row:
    def __init__(self, **attributes):
        for attribute, value in attributes.items():
            setattr(self, attribute, value)

In each iteration of our for loop, we're assigning a new attribute on our Row object (remember self points to our class instance). The attribute names and values come from the keys and values in the attributes dictionary (which was created by that ** operator).

Now our Row class can store arbitrary attributes, as we hoped:

>>> row = Row(id=4, name="duck", action="quack", color="purple")

But why use setattr? Aren't there other ways to dynamically assign attributes in Python?

Why not update __dict__ directly?

Class instances have methods (to give them functionality) and attributes (to store their data). Class instances in Python store their attributes in a dictionary, called __dict__:

>>> row = Row(id=4, name="duck", action="quack", color="purple")
>>> row.__dict__
{'id': 4, 'name': 'duck', 'action': 'quack', 'color': 'purple'}

So instead of using setattr, couldn't we just update this dictionary directly?

class Row:
    def __init__(self, **attributes):
        for attribute, value in attributes.items():
            self.__dict__[attribute] = value

This does work:

>>> row = Row(id=4, name="duck", action="quack", color="purple")

But it won't always work.

Attribute assignment uses __dict__ most of the time, but not all of the time.

One counter-example is the use of properties in Python.

This Square class's width property will make a width attribute on each class instance:

class Square:
    def __init__(self, length):
        self.length = length

    def width(self):
        return self.length

    def width(self, width):
        self.length = width

But the width attribute doesn't exist in the __dict__ dictionary on each instance:

>>> s = Square(4)
>>> s.width
>>> s.__dict__
{'length': 4}
>>> s.width = 6
>>> s.__dict__
{'length': 6}

Using setattr to assign to our width attribute works:

>>> setattr(s, "width", 3)
>>> s.__dict__
{'length': 3}

But updating __dict__ to assign to our width attribute doesn't work:

>>> s.__dict__['width'] = 10
>>> s.width
>>> s.__dict__
{'length': 3, 'width': 10}

We ended up with a width key in __dict__ that doesn't correspond to our width attributes actual value!

Properties are just one example of an attribute that doesn't live in __dict__. Classes that use __slots__ don't even have a __dict__ dictionary!

>>> class Point:
...     __slots__ = ('x', 'y')
...     def __init__(self, x, y):
...         self.x, self.y = x, y
>>> p = Point(1, 2)
>>> p.x
>>> p.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute '__dict__'. Did you mean: '__dir__'?

Why not call __setattr__?

The settarr function relies on the __setattr__ dunder method under the hood. So why not call __setattr__ directly?

class Row:
    def __init__(self, **attributes):
        for attribute, value in attributes.items():
            self.__setattr__(attribute, value)

This does work:

>>> row = Row(id=4, name="duck", action="quack", color="purple")
>>> row.color

But it's not the recommended way to set an attribute. But... why not?

Calling __setattr__ isn't recommended for the same reason that calling other dunder methods directly isn't recommended.

When working with a list:

>>> colors = ['blue', 'purple', 'green', 'orange']

We recommend using the built-in len and str functions:

>>> len(colors)
>>> str(colors)
"['blue', 'purple', 'green', 'orange']"

Instead of calling the __len__ and __str__ dunder methods:

>>> colors.__len__()
>>> colors.__str__()
"['blue', 'purple', 'green', 'orange']"

Dunder methods are for conveying information to Python; they're not for us to call.

Dunder methods are the way higher-level operations in Python are implemented. We're expected to use those higher-level operations instead of directly calling the dunder methods they correspond to.

Don't use setattr if you can avoid it

If you need to dynamically set an attribute, setattr is the most typical way to do so in Python.

But keep in mind that you shouldn't use setattr for just any attribute assignment.

For example this is a strange thing to see in Python:

>>> setattr(row, "color", "purple")

Here we're making a "color" string and using it to assign "purple" to the color attribute on our row object via setattr.

We're assigning an attribute here, but the attribute isn't dynamic: we know its name!

It would be better to use a typical attribute assignment instead:

>>> row.color = "purple"

If you don't need to assign an attribute dynamically, use a . to do a plain old attribute assignment. Python's built-in setattr function is for the rare case when your attribute assignment must be done dynamically.

Use Python's setattr function for dynamic attribute assignment

Python's built-in setattr function is for assigning attributes dynamically.

If you have a variable with a string representing an attribute name, you might need the built-in setattr function:

>>> settatr(some_object, some_attribute_name, some_value)

But do keep in mind that setting an attribute dynamically is an unusual thing to do. If you know the attribute name ahead of time (as you usually would) then you should use a regular attribute assignment instead.

Write more Pythonic code

Need to fill-in gaps in your Python skills? I send regular emails designed to do just that.