__setattr__ and __delattr__
__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] = valueinside__setattr__—self.__dict__triggers another__setattr__call. Useobject.__setattr__(self, name, value)instead. The same applies to__delattr__— useobject.__delattr__(self, name). -
__setattr__vs__getattr__:__setattr__fires on every attribute assignment.__getattr__only fires when an attribute is not found through normal lookup. Usehasattr(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 explicitdel obj.attrstatement. Do not conflate the two. -
Cannot return a non-None value. Both methods must return
None. Returning anything else raises aTypeError. -
Inside
__init__, everyself.attr = valuecall triggers__setattr__. If you have strict validation in__setattr__, you may need to useobject.__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 forobj[key] = value; mirrors__setattr__for subscript assignment.