Summary
setattr is a built‑in function that assigns an attribute by name, while __set__ is part of the descriptor protocol and controls how attribute assignment behaves when the attribute is managed by a descriptor. They operate at different layers of Python’s attribute‑access machinery.
Root Cause
The confusion comes from the fact that both appear during attribute assignment but serve fundamentally different purposes.
setattr(obj, name, value)directly sets an attribute on an object.__set__(self, obj, value)is invoked automatically when the attribute being assigned is a descriptor.- They operate at different abstraction levels: one is a function call, the other is a protocol hook.
Why This Happens in Real Systems
Real Python systems often mix plain attributes and descriptor‑managed attributes (e.g., properties, ORM fields, validators). This leads to:
- Different assignment paths depending on whether the attribute is a descriptor.
- Unexpected behavior when developers assume
setattrbypasses descriptor logic (it does not—setattrstill triggers__set__if the target attribute is a descriptor). - Complex attribute resolution chains because Python checks for descriptors before writing to
__dict__.
Real-World Impact
- Frameworks like Django and SQLAlchemy rely heavily on descriptors, so attribute assignment triggers validation, type conversion, or database tracking.
- Misunderstanding the difference can cause silent bugs, such as:
- Assignments not persisting because the descriptor stores data elsewhere.
- Infinite recursion when overriding
__setattr__incorrectly. - Security issues when bypassing validation logic unintentionally.
Example or Code (if necessary and relevant)
class Descriptor:
def __set__(self, obj, value):
print("Descriptor __set__ called")
obj.__dict__['x'] = value
class A:
x = Descriptor()
a = A()
setattr(a, 'x', 10) # Triggers Descriptor.__set__
How Senior Engineers Fix It
- Understand the descriptor protocol deeply (i.e.,
__get__,__set__,__delete__). - Trace attribute resolution order to know when descriptors intercept assignment.
- Use descriptors intentionally for validation, lazy loading, or computed attributes.
- Avoid overriding
__setattr__unless necessary, and when doing so, delegate correctly to avoid recursion. - Document attribute behavior clearly when descriptors are involved.
Why Juniors Miss It
- They assume attribute assignment is always a simple dictionary write.
- They confuse
__set__with__setattr__, which are unrelated. - They don’t realize that many built‑in features (e.g.,
property) are descriptors under the hood. - They rarely inspect Python’s descriptor invocation rules, which are not obvious without reading the documentation.