# Python descriptors **Python descriptors** are objects that implement `__get__`, `__set__`, or `__delete__` to intercept attribute access. They power properties, methods, static methods, and class variables—any time you access an attribute on an instance or class, the descriptor protocol may run. Descriptors let you customize how attributes are stored, computed, validated, or accessed without explicit getter/setter function calls. ## Example This example implements a descriptor that validates and stores an age attribute. ```python # run: python3 descriptors.py # description: descriptor for attribute validation and storage class ValidatedInt: def __init__(self, name, min_val=0, max_val=100): self.name = name self.min_val = min_val self.max_val = max_val self.data = {} def __get__(self, obj, objtype=None): if obj is None: return self return self.data.get(id(obj)) def __set__(self, obj, value): if not isinstance(value, int): raise TypeError(f"{self.name} must be int") if not (self.min_val <= value <= self.max_val): raise ValueError(f"{self.name} must be {self.min_val}-{self.max_val}") self.data[id(obj)] = value def __delete__(self, obj): del self.data[id(obj)] class Person: age = ValidatedInt("age", 0, 150) def __init__(self, name, age): self.name = name self.age = age p = Person("Alice", 30) print(f"age: {p.age}") p.age = 31 print(f"age: {p.age}") try: p.age = 200 # raises ValueError except ValueError as e: print(f"error: {e}") try: p.age = "thirty" # raises TypeError except TypeError as e: print(f"error: {e}") ``` ## Common patterns **Data descriptors** (implement `__set__` or `__delete__`): - Intercept all writes; take precedence over instance `__dict__` - Used for validation, computed properties, lazy loading **Non-data descriptors** (only `__get__`): - Don't prevent instance dict storage; instance attributes shadow them - Used for methods, computed read-only properties **Property decorator** (built-in descriptor): - `@property` creates a data descriptor for getter - `@attr.setter`, `@attr.deleter` add write/delete behavior - Cleaner syntax than manual descriptor class **Common uses**: - Validate on write (range checks, type checks) - Lazy-load expensive data on first read - Compute values on-the-fly without storage - Synchronize related attributes - Track access for logging/debugging