What are the differences between setattr and __set__?

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 setattr bypasses descriptor logic (it does not—setattr still 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.

Leave a Comment