A descriptor is an object that implements any of the methods get, set, and delete. It allows controlling access to class attributes.
Standard descriptor methods:
__get__(self, instance, owner)__set__(self, instance, value)__delete__(self, instance)Descriptors can be:
Example usage:
class OnlyPositive: def __init__(self): self._name = '_value' def __get__(self, instance, owner): return instance.__dict__[self._name] def __set__(self, instance, value): if value < 0: raise ValueError('Value must be >= 0') instance.__dict__[self._name] = value class Account: value = OnlyPositive() def __init__(self, value): self.value = value acc = Account(10) acc.value = -1 # ValueError!
property is just syntactic sugar for creating descriptors at the class level.
What happens if a descriptor is created as an instance attribute instead of a class attribute?
Many believe that the descriptor will work, but this is not the case.
The correct answer:
A descriptor works only if defined directly as a class attribute. If a descriptor is assigned as an instance attribute, the get/set methods will not be called — it will behave like a regular attribute access.
Story
Using property instead of a full descriptor
To validate and associate attributes, the developer used a simple property, leading to frequent duplication of logic and inability to reuse code among different classes.
Story
Not adhering to immutability when reusing storage
The descriptor stored data in an internal attribute of its own class (self.x), rather than in the internal storage of the instance, causing the attribute to become "shared", and different instances of the class overwrote each other's values — data "leaked" between objects.
Story
Confusion between a data descriptor and a non-data descriptor
In a complex hierarchy, the implementation of set was missed, causing a regular instance attribute to overshadow the descriptor, breaking the entire validation mechanism — the bug did not manifest always, making it hard to debug.