patternpythonModeratepending
Python Descriptor Protocol for Reusable Property Logic
Viewed 0 times
descriptor__get____set____set_name__propertyvalidationmetaprogramming
Problem
Multiple classes need the same property validation logic (type checking, range validation, etc.). Repeating @property definitions across classes violates DRY.
Solution
Use descriptors for reusable attribute behavior:
# Typed descriptor
class Typed:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __set_name__(self, owner, name):
self.name = name
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'{self.name} must be {self.expected_type.__name__}, '
f'got {type(value).__name__}'
)
obj.__dict__[self.name] = value
# Validated range descriptor
class Range:
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):
if obj is None:
return self
return obj.__dict__.get(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}')
if self.max_val is not None and value > self.max_val:
raise ValueError(f'{self.name} must be <= {self.max_val}')
obj.__dict__[self.name] = value
# Usage
class Product:
name = Typed('name', str)
price = Range(min_val=0)
quantity = Range(min_val=0, max_val=10000)
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
p = Product('Widget', 9.99, 100) # OK
p.price = -1 # ValueError: price must be >= 0Why
Descriptors are how Python implements @property, @classmethod, and @staticmethod under the hood. They let you create reusable attribute behavior that works across multiple classes.
Gotchas
- Descriptors must be class attributes, not instance attributes
- __set_name__ (Python 3.6+) automatically captures the attribute name - no need to pass it twice
Context
Building reusable attribute validation in Python
Revisions (0)
No revisions yet.