A descriptor is a class attribute whose type implements attribute-access hooks such as __get__ or __set__.
Python descriptors: field validation and attribute precedence
Operation contract
The managed quantity accepts exact integers between zero and one thousand. The descriptor records its field name during class creation, then validates before writing instance storage. Because it defines __set__, normal attribute lookup gives this data descriptor precedence over an instance dictionary entry with the public name. That prevents an accidental public-name assignment from bypassing this particular access path.
Failure and ownership boundary
This is an application convention, not a security boundary. A caller with the object can edit its private dictionary directly or replace class behavior. Class access returns the descriptor itself; forgetting that case can attempt to read storage from None. Python properties: validate mutation at the public attribute boundary uses the same attribute-management mechanism for a single field.
Working program
class BoundedQuantity:
def __set_name__(self, owner, name):
self.storage = "_" + name
def __get__(self, instance, owner=None):
if instance is None:
return self
return instance.__dict__[self.storage]
def __set__(self, instance, value):
if type(value) is not int or not 0 <= value <= 1000:
raise ValueError("quantity out of range")
instance.__dict__[self.storage] = value
class DispatchLine:
quantity = BoundedQuantity()
def __init__(self, quantity):
self.quantity = quantity
line = DispatchLine(12)
try:
line.quantity = True
except ValueError:
print("boolean rejected")
line.__dict__["quantity"] = 999
print(line.quantity)
print(isinstance(DispatchLine.quantity, BoundedQuantity))Output
boolean rejected
12
TrueCosts and limits
For these small integers, validation has bounded work and each instance stores one field value. Attribute hook calls add dispatch overhead; benchmark the application rather than assigning a universal descriptor cost.
Common Mistakes
- Handle class access where instance is None.
- Descriptors do not prevent a caller from changing private storage.
Connected lessons
Python properties: validate mutation at the public attribute boundary, Python classes: keep instance state separate from class state, Python super and method resolution: cooperate across mixins.
Follow the related contract
Python staticmethod and classmethod: choose the bound receiver, Python object lookup interview: descriptor precedence before instance state.
