Skip to main content
Descriptors let objects customize attribute lookup, storage, and deletion. They’re the mechanism behind properties, methods, static methods, class methods, and __slots__.

What is a Descriptor?

A descriptor is any object that defines __get__(), __set__(), or __delete__() methods:

Simple Example

Dynamic Lookups

Descriptors run computations instead of returning constants:
Usage:

Managed Attributes

Control access to instance data with validation:
Now all age access is logged:

Customized Names

Use __set_name__() to automatically capture attribute names:

Data Validation

Descriptor Types

Data vs Non-Data Descriptors

Data descriptors define both __get__() and __set__():
  • Take precedence over instance dictionary
  • Used for managed attributes
Non-data descriptors define only __get__():
  • Instance dictionary takes precedence
  • Used for methods and functions

Property Implementation

Here’s how Python’s property() works under the hood:
Usage:

Common Use Cases

Lazy Properties

Compute values only when needed:

Type Checking

Best Practices

Descriptor Pitfalls:
  1. Descriptors only work as class variables, not instance variables
  2. Always handle the obj is None case in __get__()
  3. Be careful with __set_name__() - it’s called at class creation time
When to Use Descriptors:
  • ✅ Repeated validation logic across multiple classes
  • ✅ Computed attributes with caching
  • ✅ Attribute access logging/monitoring
  • ✅ Type checking and coercion
  • ❌ Simple properties (use @property instead)
  • ❌ One-off custom behavior (use __getattribute__ override)

Summary

Key points about descriptors:
  1. Define __get__(), __set__(), or __delete__() to create a descriptor
  2. Use __set_name__() to capture the attribute name automatically
  3. Data descriptors override instance dictionary
  4. Non-data descriptors are overridden by instance dictionary
  5. Descriptors power properties, methods, classmethod, staticmethod, and slots