Python Descriptors
Control attribute access with descriptors.
Low-level attribute control.
Basic Descriptor
```python class Positive: def __init__(self, name): self.name = name def __get__(self, obj, objtype=None): return obj.__dict__.get(self.name, 0) def __set__(self, obj, value): if value < 0: raise ValueError("Must be positive") obj.__dict__[self.name] = value
class Account: balance = Positive("balance") def __init__(self, balance): self.balance = balance
account = Account(1000) print(account.balance) # 1000 # account.balance = -500 # Error! ```
Type Validation
```python class TypedProperty: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): if not isinstance(value, self.expected_type): raise TypeError(f"Expected {self.expected_type}") obj.__dict__[self.name] = value
class Person: name = TypedProperty("name", str) age = TypedProperty("age", int) def __init__(self, name, age): self.name = name self.age = age
person = Person("Tom", 25) # person.age = "25" # Error! Must be int ```
Cached Property
```python class CachedProperty: def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): if obj is None: return self # Cache result value = self.func(obj) obj.__dict__[self.func.__name__] = value return value
class DataProcessor: @CachedProperty def expensive_calculation(self): print("Computing...") return sum(range(1000000))
processor = DataProcessor() print(processor.expensive_calculation) # Computing... 499999500000 print(processor.expensive_calculation) # 499999500000 (cached) ```
Remember
- Descriptors control attribute access - Properties are built on descriptors - Very powerful but complex