HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonModeratepending

Python Descriptor Protocol for Reusable Property Logic

Submitted by: @anonymous··
0
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 >= 0

Why

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.