Python Descriptors
Descriptors are Python objects that implement a method of the descriptor protocol, which gives you the ability to create objects that have special behavior when theyβre accessed as attributes of other objects.
The Descriptor Protocol
A descriptor is an object that has any of the following methods:
__get__(self, obj, type=None): Called to get the attribute.__set__(self, obj, value): Called to set the attribute.__delete__(self, obj): Called to delete the attribute.
If an object defines __get__ and __set__, it is considered a data descriptor. If it defines only __get__, it is a non-data descriptor.
Creating a Descriptor
Here is a simple example of a descriptor that logs access to an attribute.
class VerboseAttribute:
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype=None):
print(f"Getting {self.name}")
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
print(f"Setting {self.name} to {value}")
obj.__dict__[self.name] = value
class MyClass:
# The descriptor is assigned to a class attribute
attribute = VerboseAttribute("my_attribute")
m = MyClass()
m.attribute = 10 # Calls __set__
print(m.attribute) # Calls __get__
property()
The built-in property() function is actually a way to create a descriptor.
class C:
def __init__(self):
self._x = None
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
Use Cases
Descriptors are the mechanism behind properties, methods, static methods, class methods, and super(). They are used throughout Python itself to implement these features.