Introduction
Deep dive into descriptors for reusable property behavior with full protocol implementation.
Non-Data vs Data Descriptors
# Non-data descriptor (read-only)
class NonDataDescriptor:
def __get__(self, obj, objtype=None):
return "computed value"
# Data descriptor (overrides instance __dict__)
class DataDescriptor:
def __get__(self, obj, objtype=None):
return obj.__dict__.get("_value", "default")
def __set__(self, obj, value):
obj.__dict__["_value"] = value
Property as Data Descriptor
class Circle:
def __init__(self, radius):
self.radius = radius # Triggers __set__
@property
def diameter(self):
return self.radius * 2
@diameter.setter
def diameter(self, value):
self.radius = value / 2
Shared Descriptor Behavior
class ValidatedProperty:
def __init__(self, min_val=None, max_val=None):
self.min_val = min_val
self.max_val = max_val
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype=None):
return obj.__dict__[self.name]
def __set__(self, obj, value):
if self.min_val is not None and value < self.min_val:
raise ValueError(f"{self.name} must be >= {self.min_val}")
obj.__dict__[self.name] = value
Practice Problems
- Create validated numeric descriptor
- Implement lazy loading descriptor
- Create read-only descriptor
- Build descriptor with callback
- Use descriptors in data classes