🧱 Python Descriptors – Master Attribute Access and Control
🧲 Introduction – Why Use Descriptors?
Python classes offer flexible ways to control how attributes are accessed, modified, and deleted. If you’ve ever used:
@propertydecorators- ORMs like SQLAlchemy
- Data validation patterns
…you’ve already used descriptors, possibly without realizing it.
Descriptors give you low-level control over attribute behavior and are the building blocks for Python’s property system, class decorators, and data management frameworks.
🎯 In this guide, you’ll learn:
- What descriptors are and how they work
- How to implement
__get__,__set__, and__delete__ - The difference between data and non-data descriptors
- Real-world use cases and best practices
✅ What Is a Descriptor?
A descriptor is a class that implements any of the descriptor methods:
__get__(self, instance, owner)__set__(self, instance, value)__delete__(self, instance)
Descriptors are defined at the class level and control attribute access at the instance level.
🛠️ Creating a Simple Descriptor
class Descriptor:
def __get__(self, instance, owner):
return "Value from descriptor"
class MyClass:
attr = Descriptor()
obj = MyClass()
print(obj.attr) # Value from descriptor
✅ The descriptor’s __get__() is triggered when accessing obj.attr.
🔁 Full Descriptor with __get__ and __set__
class Name:
def __init__(self):
self._value = ""
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError("Name must be a string")
self._value = value
class Person:
name = Name()
p = Person()
p.name = "Alice"
print(p.name) # Alice
✅ You can enforce data validation and custom logic during attribute assignment.
🔄 Descriptor Methods Overview
| Method | Triggered By |
|---|---|
__get__() | Attribute access (obj.x) |
__set__() | Attribute assignment (obj.x = val) |
__delete__() | del obj.x |
⚖️ Data vs Non-Data Descriptors
| Type | Implements | Overrides Instance Dictionary? |
|---|---|---|
| Data Descriptor | __get__ and __set__ | ✅ Yes |
| Non-Data Descriptor | Only __get__ | ❌ No – instance dict wins |
✅ Example:
class NonData:
def __get__(self, instance, owner):
return "non-data"
class MyClass:
attr = NonData()
obj = MyClass()
obj.__dict__['attr'] = "instance attr"
print(obj.attr) # instance attr (wins over non-data descriptor)
📌 Non-data descriptors are overridden by instance attributes.
📦 Real-World Use Case – Typed Fields
class Typed:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"Expected {self.expected_type}")
instance.__dict__[self.name] = value
class Product:
name = Typed("name", str)
price = Typed("price", float)
p = Product()
p.name = "Laptop"
p.price = 999.99
✅ Descriptors power custom validation logic at the class level—perfect for ORM fields, data models, and configuration systems.
🧠 Descriptors vs @property
| Feature | @property | Descriptor |
|---|---|---|
| Syntax | Method decorator | Separate class |
| Reusable | No (per-property definition) | Yes (general-purpose field classes) |
| Flexibility | Basic getters/setters | Full control + reuse |
✅ Use descriptors when you need reuse, validation, or complex behavior across multiple attributes.
📘 Best Practices
| ✅ Do This | ❌ Avoid This |
|---|---|
| Use descriptors for reusable field logic | Using property when multiple reuse needed |
Prefer __dict__ for storage | Creating recursive calls in __get__ |
Implement __set_name__() for cleaner setup | Hardcoding attribute names |
| Combine with metaclasses or decorators | Nesting descriptors without documentation |
📌 Summary – Recap & Next Steps
Descriptors allow you to build powerful and reusable attribute management logic. They’re used in the core of Python’s own language features and widely adopted in frameworks and libraries.
🔍 Key Takeaways:
- ✅ Descriptors are classes that define
__get__,__set__, or__delete__ - ✅ Use for attribute control, validation, caching, or logging
- ✅ Data descriptors override instance attributes; non-data do not
- ✅ Ideal for frameworks, ORMs, and custom data models
⚙️ Real-World Relevance:
Used in Django ORM fields, SQLAlchemy columns, Pydantic models, and property-like patterns.
❓ FAQ – Python Descriptors
❓ What is a Python descriptor?
✅ A class that defines any of __get__, __set__, or __delete__ methods, used to manage attribute access.
❓ When should I use a descriptor?
✅ When you need reusable logic for attribute access/validation across many classes.
❓ What’s the difference between a data and non-data descriptor?
- Data descriptors implement
__set__()(and override instance vars) - Non-data descriptors only implement
__get__()(and are overridden by instance vars)
❓ How are descriptors used in frameworks?
✅ Descriptors power field definitions, validation layers, and configuration systems.
❓ What is __set_name__?
✅ A special method added in Python 3.6+ that informs the descriptor of its attribute name:
def __set_name__(self, owner, name):
self.name = name
Share Now :
