Python28 min read
Python Descriptors
Learn descriptors (the engine behind properties): define __get__ and __set__ to control attribute access, validation, and caching.
David Miller
August 16, 2025
11.6k451
Descriptors are a low-level feature that powers many high-level tools in Python.
Important fact:
- `@property` is implemented using descriptors.
A descriptor is any object that defines:
- __get__
- __set__
- __delete__ (optional)
## Example: Positive-only descriptor
```python
class Positive:
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype=None):
return obj.__dict__.get(self.name, 0)
def __set__(self, obj, value):
if value < 0:
raise ValueError("Value must be positive")
obj.__dict__[self.name] = value
class Account:
balance = Positive("balance")
def __init__(self, balance):
self.balance = balance
acc = Account(1000)
print(acc.balance)
# acc.balance = -50 # error
```
## Example: type validation descriptor
```python
class Typed:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, obj, objtype=None):
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"Expected {self.expected_type.__name__}")
obj.__dict__[self.name] = value
class Person:
name = Typed("name", str)
age = Typed("age", int)
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Tom", 25)
print(p.name, p.age)
```
## Example: caching descriptor (simple cached property)
```python
class CachedProperty:
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.name in obj.__dict__:
return obj.__dict__[self.name]
value = self.func(obj)
obj.__dict__[self.name] = value
return value
class DataProcessor:
@CachedProperty
def expensive(self):
print("Computing...")
return sum(range(1_000_000))
dp = DataProcessor()
print(dp.expensive)
print(dp.expensive)
```
Expected output:
```
Computing...
499999500000
499999500000
```
## Graph: descriptor role
```mermaid
flowchart LR
A[Access obj.attr] --> B[Descriptor __get__]
C[Assign obj.attr = x] --> D[Descriptor __set__]
B --> E[Return value]
D --> F[Validate + store]
```
## Key points
- Descriptors control attribute access deeply
- Properties are built on descriptors
- Powerful but advanced, use only when needed
#Python#Advanced#OOP