- 🧠
setattr()calls a descriptor’s__set__()if the attribute is a data descriptor. - ⚙️
setattr()internally triggers__setattr__(), allowing custom assignment behaviors. - 💡 Python descriptors allow reusable logic like validation, lazy loading, and protection.
- ⚠️ Direct access to
__dict__bypasses descriptor protocols. - 🚦 Use descriptors for control; use
setattr()for flexibility and changing environments.
Controlling Python Attributes: setattr() vs __set__()
Python provides ways to control how you add and manage attributes on objects. Two of these tools, setattr() and the __set__() method in descriptors, let you set attributes. But they work in different ways and for different reasons. Knowing when and how to use them can help you write more flexible, clean, and easier-to-maintain code.
Understanding setattr() in Python
Syntax and How It Works
The setattr() function lets you assign a value to an attribute of an object while your program runs. The syntax is as follows:
setattr(object, name, value)
This is the same as writing:
object.name = value
But the regular dot notation needs the attribute name to be a valid name written in your code. setattr() lets you pass the name as a string. This helps when attribute names are not known until the program runs. For example, think about reading JSON data, user input, or object setups that change based on data rules.
Practical Use Cases for setattr()
1. Flexible Attribute Assignment
You can easily add attributes with names that change at runtime.
class Config: pass
config = Config()
params = {"debug": True, "timeout": 30}
for key, value in params.items():
setattr(config, key, value)
This is very useful when building APIs, loading settings files, or changing behavior based on specific environments. Instead of writing config.debug = True and config.timeout = 30, you can assign values as needed from a dictionary.
2. Working with Flexible Data Models
Many programming languages work with formats like JSON, where field names are not set ahead of time.
import json
class User:
pass
data = '{"name": "Alice", "age": 28}'
user_dict = json.loads(data)
user = User()
for field, value in user_dict.items():
setattr(user, field, value)
This helps build flexible models or event objects. It is very useful in frameworks or libraries that follow data rules.
3. Mass Assignment in Bulk Object Population
When you set up an object with many attributes, setattr() allows for a cleaner loop. This is better than manually assigning each value.
class Person:
def __init__(self, **kwargs):
for attr, value in kwargs.items():
setattr(self, attr, value)
p = Person(name="Tom", age=22)
This code is shorter and helps with building systems that take in data or use settings defined by users.
What Happens Internally?
When you call setattr(obj, 'attr', value), Python internally calls:
type(obj).__setattr__(obj, 'attr', value)
This calls the object's __setattr__() method. This makes it possible to track, stop, or change how attributes get assigned.
class Tracker:
def __setattr__(self, name, value):
print(f"Intercepted setting {name} to {value}")
super().__setattr__(name, value)
t = Tracker()
setattr(t, 'x', 42) # Output: Intercepted setting x to 42
Changing __setattr__() is helpful for watching what happens, checking data, logging, and making sure certain parts of classes cannot be changed.
Limitations of setattr()
Keep one main limit in mind: setattr() works with Python's general way of setting attributes and respects how attributes are looked up. But it does not work directly with non-data descriptors unless they allow writing. Also:
- If the attribute does not use a descriptor, assignment puts the key-value pair in
__dict__by default. - But if the class has a data descriptor (meaning it has a
__set__()method),setattr()will call that method.
This covers most cases. However, more advanced custom descriptors follow more detailed rules for how they work. And this leads us to…
Descriptors and the Role of __set__()
What Is a Descriptor?
A Python descriptor is any object that uses one or more of these special methods:
__get__(self, instance, owner)__set__(self, instance, value)__delete__(self, instance)
When you assign such an object as a class attribute, the descriptor's methods handle any access to that attribute.
Python uses descriptors widely behind the scenes. For example:
@propertyuses__get__and__set__.- Methods use descriptors to link functions to objects.
staticmethodandclassmethodare built using descriptors.
Often, descriptors hide logic like checking data, changing data, making things read-only, or hooking into attribute access.
"If a class defines a descriptor for an attribute, access to that attribute is intercepted and handled by the descriptor’s
__get__,__set__, or__delete__methods."
— Python Descriptor HowTo Guide
How __set__() Works
If a descriptor has a __set__() method, Python will call it whenever you assign a value to the related attribute. For example:
class Descriptor:
def __set__(self, instance, value):
print(f"__set__ called with {value}")
instance.__dict__['value'] = value
class MyClass:
attr = Descriptor()
obj = MyClass()
obj.attr = 10 # Triggers Descriptor.__set__()
If the class has a data descriptor (one with both __get__ and __set__), Python gives it first say when looking up and assigning attributes. This happens over the object's own __dict__.
Validating and Filtering With Descriptors
Let's look at a practical example of how to control assignments using a descriptor that checks data:
class Positive:
def __set_name__(self, owner, name):
self.name = '_' + name
def __get__(self, obj, objtype=None):
return getattr(obj, self.name)
def __set__(self, obj, value):
if value < 0:
raise ValueError("Value must be positive")
setattr(obj, self.name, value)
class Product:
price = Positive()
p = Product()
p.price = 100 # OK
p.price = -25 # Raises ValueError
Why Use Descriptors?
Descriptors give you a way to reuse and package logic for how attributes behave. They are especially useful for:
- Validation: Making sure attributes hold the right data types or values.
- Transformations: Automatically changing input into a needed form (e.g., dates or short web addresses).
- Lazy loading: Making attributes that figure out their values only when you first use them.
- Caching: Storing results of big calculations and using them again.
- Auditing: Hooking into attribute access to log or track changes.
"Descriptors are a powerful, general-purpose protocol, and Python uses it behind the scenes to implement functions, class methods, static methods, and properties."
— Hettinger, R. (2004)
Key Mechanic Differences Between setattr() and __set__()
Attribute Resolution Order
Here is Python’s strategy for finding and using methods:
Case 1: obj.attr = value
- If
attris on the class and is a data descriptor (__set__()exists):- Python calls
descriptor.__set__(obj, value).
- Python calls
- If not, Python checks if
__setattr__()is set up on the object:- If yes, that method gets called.
- If not,
obj.__dict__[attr] = valueruns.
Case 2: setattr(obj, 'attr', value)
- This calls
type(obj).__setattr__(obj, 'attr', value). - This might then call:
- The descriptor’s
__set__()method, if it applies. - The class’s custom
__setattr__()method.
- The descriptor’s
- If nothing else overrides it, the default dictionary-like action takes over.
Demonstration
See how setattr() follows the data descriptor rules:
class Descriptor:
def __set__(self, obj, value):
print("Descriptor __set__ called")
obj.__dict__['value'] = value
class MyClass:
value = Descriptor()
m = MyClass()
setattr(m, 'value', 42) # Output: Descriptor __set__ called
Tabular Comparison
| Operation | Calls __setattr__() |
Calls __set__() (Descriptor) |
|---|---|---|
obj.attr = value |
✅ | ✅ if it's a data descriptor |
setattr(obj, 'attr', value) |
✅ | ✅ if it's a data descriptor |
obj.__dict__['attr'] = value |
❌ | ❌ |
Choosing the Right Tool
When to Use setattr()
Choose setattr() when:
- You need to assign attributes with names that change.
- You are reading and changing data from other formats (e.g., JSON, YAML).
- You are writing code that writes code or making objects at runtime.
- You want to reduce how often you hard-code attribute names.
When to Use Descriptors and __set__()
Use __set__() inside descriptors when:
- You want strong control or a clear reason for how data is assigned.
- You are building reusable logic for attributes (e.g., checks, formatters).
- You are making sure interfaces work well or internal rules are followed (e.g., only allowing positive numbers).
- You need widespread control over many objects or classes.
Blending Both Approaches
Use both together when systems that change still need clear rules:
class LimitedLength:
def __init__(self, max_len):
self.max_len = max_len
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype=None):
return obj.__dict__.get(self.name, '')
def __set__(self, obj, value):
if len(value) > self.max_len:
raise ValueError("Too long!")
obj.__dict__[self.name] = value
class User:
username = LimitedLength(10)
u = User()
setattr(u, 'username', 'shortname') # ✅ Works
setattr(u, 'username', 'toolongggggg') # ❌ Raises ValueError
Common Pitfalls and Anti-Patterns
1. Misunderstanding setattr()'s Internals
Some people think setattr() changes __dict__ directly, bypassing all other logic. It does not. It uses the object’s __setattr__() method. Also, if a descriptor is present, it uses its __set__() method too.
2. Overusing setattr() Instead of Simple Assignment
If you do not need to assign attributes with names that change, it’s better to use plain syntax:
obj.name = "John" # Preferred
setattr(obj, 'name', 'John') # Wordy unless the name changes
3. Creating Custom Descriptors Where @property Works
Not all situations need a full descriptor. For many cases, especially when an object's behavior matters, a simple @property setter is enough.
Final Recap: The Right Tool for the Right Job
| Use Case | Recommended Tool |
|---|---|
| Flexible attribute assignment | setattr() |
| Validation, control, transformation | Descriptors / __set__() |
| Logging or monitoring assignments | Override __setattr__() |
| Schema-driven object construction | setattr() + Descriptors |
| Reusable enforcement logic | Descriptors |
Understanding the difference and how setattr() and __set__() work together helps you write clearer, safer, and more flexible Python code. These tools will help you work better whether you are making a setup system, a library, or just want more clear control over your attributes.
📎 Try it: Live Python Descriptor Example
Citations
- Van Rossum, G., & Python Software Foundation. (n.d.). Python Descriptor HowTo Guide. Retrieved from https://docs.python.org/3/howto/descriptor.html
- Hettinger, R. (2004). Descriptor Protocol overview. PyCon (USA).
- Python Software Foundation. (n.d.). Built-in Functions: setattr(). Retrieved from https://docs.python.org/3/library/functions.html#setattr