🏷 Descriptors in Python
Descriptors are the mechanism behind properties, methods, static methods, class methods, and super(). They are objects that define how attribute access is handled.
🧠 What is a Descriptor?
A descriptor is any object that defines at least one of the following methods:
- __get__(self, obj, type=None)
- __set__(self, obj, value)
- __delete__(self, obj)
If an object defines these, it is considered a descriptor.
🛠 Creating a Descriptor
Let's create a descriptor that enforces type checking.
class Integer:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError(f"{self.name} must be an integer")
instance.__dict__[self.name] = value
class Point:
x = Integer('x')
y = Integer('y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 5 # OK
# p.y = "hello" # Raises TypeError
🏷 __set_name__
In Python 3.6+, __set_name__ allows the descriptor to know the name of the attribute it is assigned to.
class Integer:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError(f"{self.name} must be an integer")
instance.__dict__[self.name] = value
class Point:
x = Integer() # No need to pass 'x'
y = Integer()
🧩 Data vs Non-Data Descriptors
- Data Descriptor: Defines
__set__or__delete__. Always overrides the instance dictionary. - Non-Data Descriptor: Only defines
__get__(like methods). Can be overridden by the instance dictionary.
📝 Summary
- ✅ Under the Hood: Powering many Python features like
@property. - ✅ Reusable Logic: Encapsulate attribute access logic (validation, lazy loading).
- ✅ Control: Fine-grained control over attribute access.
Created with ❤️ by Pynfinity