The setattr
function is one of the lesser used built-in functions in Python.
Let's talk about Python's built-in setattr
function.
We'd like to make a class that works like this:
>>> row = Row(id=4, name="duck", action="quack", color="purple")
>>> row.id
4
>>> row.name
'duck'
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
'purple'
We need some way to dynamically assign an attribute!
setattr
function to the rescuePython'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")
>>> row.id
4
But why use setattr
?
Aren't there other ways to dynamically assign attributes in Python?
__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")
>>> row.name
'duck'
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
@property
def width(self):
return self.length
@width.setter
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
4
>>> 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
3
>>> 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
1
>>> p.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute '__dict__'. Did you mean: '__dir__'?
__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.name
'duck'
>>> row.color
'purple'
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)
4
>>> str(colors)
"['blue', 'purple', 'green', 'orange']"
Instead of calling the __len__
and __str__
dunder methods:
>>> colors.__len__()
4
>>> 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.
setattr
if you can avoid itIf 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.
setattr
function for dynamic attribute assignmentPython'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.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.