Complete guide to Python's __setattr__ method covering attribute assignment control, validation, and dynamic attributes.
Last modified April 8, 2025
This comprehensive guide explores Python’s setattr method, the special method that controls attribute assignment. We’ll cover basic usage, attribute validation, dynamic attributes, and practical examples.
The setattr method is called when an attribute assignment is attempted on an object. It intercepts all attribute assignments, including those in init.
Key characteristics: it receives the attribute name and value as arguments, must handle attribute storage manually, and can prevent or transform attribute assignments. It’s called for all attribute assignments.
Here’s a simple implementation showing how setattr intercepts attribute assignments. It demonstrates the basic structure and requirements.
basic_setattr.py
class Person: def setattr(self, name, value): print(f"Setting attribute {name} to {value}") super().setattr(name, value)
p = Person() p.name = “Alice” p.age = 30 print(p.name, p.age)
This example shows the basic pattern: print a message, then delegate to the parent class’s setattr using super(). Without this delegation, attributes wouldn’t be stored.
The output shows the interception of both attribute assignments before they’re stored. This is the foundation for more advanced attribute control.
setattr can validate attribute values before allowing them to be set, enforcing business rules or type constraints.
validation.py
class Temperature: def setattr(self, name, value): if name == “celsius”: if not isinstance(value, (int, float)): raise TypeError(“Temperature must be numeric”) if value < -273.15: raise ValueError(“Temperature below absolute zero”) super().setattr(name, value)
temp = Temperature() temp.celsius = 25 # Valid
This temperature class validates that celsius values are numeric and above absolute zero. Invalid assignments raise exceptions before storage occurs.
The validation happens before calling the parent’s setattr, preventing invalid states. This pattern is useful for domain models.
setattr can make certain attributes read-only by preventing their modification after initial assignment.
readonly.py
class Constants: def init(self): super().setattr("_values", {}) self._values[“PI”] = 3.14159
def __setattr__(self, name, value):
if name in self._values:
raise AttributeError(f"Cannot modify {name}")
super().__setattr__(name, value)
const = Constants() print(const._values[“PI”]) # 3.14159
const.E = 2.71828 # Allowed
This constants class stores protected values in a dictionary and prevents their modification. New attributes can still be added normally.
The trick is using super().setattr in init to bypass the custom setattr for initial setup.
setattr can dynamically transform attribute values or create derived attributes when assignments occur.
dynamic.py
class CaseInsensitiveDict: def setattr(self, name, value): lower_name = name.lower() if lower_name == “data”: super().setattr(“data”, {}) else: self.data[lower_name] = value
def __getattr__(self, name):
return self.data.get(name.lower())
d = CaseInsensitiveDict() d.Color = “blue” d.COLOR = “red” print(d.color) # “red” (last assignment wins)
This dictionary-like object stores attributes case-insensitively. All assignments go into a data dictionary using lowercase keys.
Note the special handling for the ‘data’ attribute itself, which needs to be stored normally to avoid infinite recursion.
setattr can completely prevent new attribute creation, making objects strictly follow a predefined schema.
prevent.py
class StrictPerson: slots = (“name”, “age”)
def __setattr__(self, name, value):
if name not in self.__slots__:
raise AttributeError(f"Cannot add attribute {name}")
super().__setattr__(name, value)
p = StrictPerson() p.name = “Bob” # Allowed p.age = 40 # Allowed
This class combines slots with setattr to create a strictly controlled object. Only predefined attributes can be set.
The slots declaration provides memory efficiency while setattr enforces the schema at runtime.
Always call super().setattr: Unless intentionally blocking storage
Avoid infinite recursion: Use super() or direct dict access carefully
Document behavior: Clearly document any special assignment logic
Consider performance: setattr adds overhead to all assignments
Use @property when possible: For simple cases, properties may be cleaner
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.