__setattr__ and __delattr__

Updated March 27, 2026 · Dunder Methods
python attribute-access

__setattr__ and __delattr__ let you intercept attribute assignment and deletion on any object. Every time you write obj.attr = value or del obj.attr, Python calls one of these methods — giving you a hook to validate, redirect, or block the operation.

__setattr__

def __setattr__(self, name: str, value: Any) -> None

Python calls __setattr__ on every attribute assignment, both from outside the class and from within __init__ or any other method via self.attr = value. The method receives the attribute name as a string and the value being assigned. It must return None.

class Person:
    def __setattr__(self, name, value):
        if name == "age" and not isinstance(value, int):
            raise TypeError(f"age must be int, got {type(value).__name__}")
        object.__setattr__(self, name, value)

p = Person()
p.age = 30          # OK
p.age = "oops"      # TypeError: age must be int, got str

__delattr__

def __delattr__(self, name: str) -> None

Python calls __delattr__ every time del obj.attr runs. It receives the attribute name as a string and must return None. It is not called during garbage collection or when __del__ runs — only when an explicit del statement targets an attribute.

class Protected:
    def __delattr__(self, name):
        raise AttributeError(f"cannot delete {name!r}")

p = Protected()
del p.anything  # AttributeError: cannot delete 'anything'

Common Patterns

Attribute validation

Restrict which attributes can be set and enforce types or value constraints before accepting an assignment.

class Point:
    def __setattr__(self, name, value):
        if name in ("x", "y") and not isinstance(value, (int, float)):
            raise TypeError(f"{name} must be numeric")
        object.__setattr__(self, name, value)

pt = Point()
pt.x = 10
pt.y = 4.5
pt.x = "two"  # TypeError: x must be numeric

Read-only attributes

Allow an attribute to be set exactly once — on first assignment — and raise an error on any subsequent attempt.

class Config:
    def __setattr__(self, name, value):
        if hasattr(self, name):
            raise AttributeError(f"{name!r} is read-only")
        object.__setattr__(self, name, value)

c = Config()
c.debug = True    # OK
c.debug = False   # AttributeError: 'debug' is read-only

Lazy loading with __getattr__ and __setattr__

__setattr__ works alongside __getattr__ to implement lazy attribute initialization — creating the attribute on first access and caching the result.

class Lazy:
    def __getattr__(self, name):
        if name.startswith("_"):
            raise AttributeError(name)
        object.__setattr__(self, name, f"lazy_{name}")
        return self.name

l = Lazy()
print(l.foo)   # lazy_foo
print(l.foo)   # lazy_foo

__getattr__ only fires when an attribute is not found normally, so the first l.foo call creates the attribute via __setattr__. Subsequent accesses find foo without triggering __getattr__.

Gotchas and pitfalls

  • Infinite recursion when using self.__dict__[name] = value inside __setattr__self.__dict__ triggers another __setattr__ call. Use object.__setattr__(self, name, value) instead. The same applies to __delattr__ — use object.__delattr__(self, name).

  • __setattr__ vs __getattr__: __setattr__ fires on every attribute assignment. __getattr__ only fires when an attribute is not found through normal lookup. Use hasattr(self, name) inside __setattr__ to check if a value already exists without triggering recursion.

  • __delattr__ is not __del__: __del__ is a finalizer called during garbage collection when an object’s reference count drops to zero. __delattr__ is only called by the explicit del obj.attr statement. Do not conflate the two.

  • Cannot return a non-None value. Both methods must return None. Returning anything else raises a TypeError.

  • Inside __init__, every self.attr = value call triggers __setattr__. If you have strict validation in __setattr__, you may need to use object.__setattr__ directly for attributes that need special handling during initialization.

See Also

  • __getattr__ — called only when an attribute is not found normally; pairs with __setattr__ for lazy loading patterns.
  • __del__ — the object finalizer, often confused with __delattr__; runs during garbage collection, not on attribute deletion.
  • __setitem__ — the item-access analogue for obj[key] = value; mirrors __setattr__ for subscript assignment.