ProgrammingSenior Python Developer

What are descriptors in Python? How are they implemented, for what purposes are they used, what is the difference from property, and what are typical mistakes when using them?

Pass interviews with Hintsage AI assistant

Answer

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:

  • Classic (data descriptors) — implement set, have priority over regular attributes;
  • Non-classic (non-data descriptors) — implement only get, are overshadowed by regular attributes.

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.

Trick question

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.

Examples of real mistakes due to lack of knowledge of the nuances of the topic


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.