Understanding __get__ and __set__ and Python descriptors
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Python descriptors are the mechanism behind many attribute access features, including property, bound methods, and framework-managed fields. They let you intercept get and set operations with reusable logic. Understanding __get__ and __set__ gives you a clear model for validation, lazy loading, and attribute-level behavior control.
Descriptor Protocol Fundamentals
An object is a descriptor if it defines one or more of these methods:
- '
__get__(self, instance, owner)' - '
__set__(self, instance, value)' - '
__delete__(self, instance)'
When assigned as a class attribute, Python routes attribute access through these methods.
This shows how reading d.field invokes descriptor logic instead of direct dictionary lookup.
Data Versus Non-Data Descriptors
Descriptor precedence depends on whether __set__ or __delete__ exists.
- data descriptor: defines
__set__or__delete__ - non-data descriptor: defines only
__get__
Data descriptors override instance dictionary values. Non-data descriptors can be shadowed by instance attributes.
This precedence rule explains many surprising attribute behaviors.
Build a Validation Descriptor
Descriptors are useful for reusable validation rules.
__set_name__ gives each descriptor instance the attribute name it was assigned to.
Relationship to property
property is implemented with descriptors. Getter and setter decorators attach methods that act like __get__ and __set__ behavior.
Understanding descriptor protocol makes property feel less magical and easier to debug.
Attribute Lookup Order Overview
For obj.attr, Python roughly checks:
- data descriptor in class
- instance dictionary
- non-data descriptor in class
- regular class attribute
- base classes by method resolution order
This lookup order is the practical foundation for debugging descriptor conflicts.
Framework Usage Patterns
Descriptors appear in many frameworks:
- ORM fields that validate or lazily fetch values
- settings systems with typed conversion
- caching proxies that compute on first access
When you see framework attributes acting like methods or smart fields, descriptors are often involved.
Debugging Descriptor Problems
Practical debugging steps:
- inspect
type(obj).__dict__to see descriptor objects - print descriptor methods during access
- check instance
__dict__for shadowing - verify data versus non-data descriptor behavior
These checks usually reveal precedence mistakes quickly.
Common Pitfalls
A common pitfall is storing per-instance state on the descriptor object itself, which leaks values between instances. Another is forgetting to handle instance is None in __get__, which breaks class-level access. Developers also expect non-data descriptors to override instance attributes, which they do not. Missing __set_name__ usage can cause brittle storage naming. Finally, vague validation errors inside descriptors make debugging harder than needed.
Summary
- Descriptors customize attribute access via
__get__,__set__, and__delete__. - Data descriptors take precedence over instance dictionary values.
- '
propertyis a descriptor-based abstraction.' - Descriptors are excellent for reusable validation and lazy behavior.
- Lookup-order understanding is essential for debugging attribute conflicts.
- Clear state handling and error messages make descriptor code maintainable.

