Understanding __get__ and __set__ and Python descriptors
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
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.
Related reading

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.