Encapsulation
Descriptors
Advanced attribute access control via descriptor protocol
Interview: Advanced Python — tests understanding of descriptor protocol, __set_name__, and how properties/slots work under the hood
Descriptors are objects that define how attribute access works via the descriptor protocol (__get__, __set__, __delete__). They're the mechanism behind @property, __slots__, class methods, and static methods. Descriptors enable reusable, composable attribute management across multiple classes.
Descriptor Protocol
__get__(self, obj, objtype=None): Called when attribute is accessed__set__(self, obj, value): Called when attribute is assigned__delete__(self, obj): Called when attribute is deleted__set_name__(self, owner, name): Called when descriptor is assigned to a class attribute (Python 3.6+)
Data vs Non-Data Descriptors
- Data descriptor: Defines both
__get__and__set__— takes priority over instance__dict__ - Non-data descriptor: Defines only
__get__— instance__dict__takes priority - This distinction affects attribute lookup order: data descriptors > instance dict > non-data descriptors
Interview Tip
Know that @property, @classmethod, and @staticmethod are all implemented as descriptors. Understanding descriptors means understanding how Python's attribute access really works.
Use Cases
Reusable validation logic across many attributes and classes
Type enforcement for class fields (like Django model fields)
Lazy property loading (compute on first access, cache afterward)
ORM field definitions (SQLAlchemy columns are descriptors)
Implementing custom @property-like decorators for teams
Common Mistakes
Not implementing __set_name__ — descriptor doesn't know its attribute name
Storing value in descriptor instead of instance (shared across all instances!)
Not handling class-level access (obj is None in __get__)
Confusing data descriptors (with __set__) and non-data descriptors (without)
Using descriptors when @property would be simpler (overengineering for one-off cases)