Properties and descriptors
Control attribute access without changing the public syntax
Properties
A property runs methods through attribute syntax. Use it for a computed value, read-only view, or validated assignment:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
shape = Rectangle(3, 4)
print(shape.area) # 12
shape.width = 5
print(shape.area) # 20area is calculated from current dimensions, avoiding a stale stored copy. Without a setter, assigning shape.area raises AttributeError. The public name can stay stable while its implementation changes.
Setters
Validate before replacing valid state. Using the setter in __init__ applies the same rule during construction:
class RetryLimit:
def __init__(self, value):
self.value = value
@property
def value(self):
return self._value
@value.setter
def value(self, proposed):
if type(proposed) is not int or proposed < 0:
raise ValueError("expected a nonnegative integer")
self._value = proposed
limit = RetryLimit(3)
try:
limit.value = -1
except ValueError:
print(limit.value) # 3The exact int check rejects booleans and subclasses. State whether the API permits a wider numeric protocol. Use a separate storage name: reading self.value inside its own getter would recurse.
Underscores mark internal storage by convention, not access control. Setters do not prevent deliberate internal writes or synchronize multi-field transitions. Assignment can raise documented exceptions just like a method call.
Descriptors
A descriptor is stored on a class and defines __get__, __set__, or __delete__ to customize attribute access.
- Properties are data descriptors and take precedence over ordinary instance-dictionary entries, even without a setter.
- Functions are non-data descriptors; method access uses them to bind
self. Instance attributes can shadow non-data descriptors. __set_name__can provide the attribute name to a reusable validator. Store field values per instance; storing the latest value on the descriptor would share it across instances.__get__must handle class-level access, where the instance argument isNone.
Use a property for one field and a custom descriptor when several fields share an access rule.
Gotchas
Keep attribute reads reasonably cheap. Use explicit methods for slow network calls or major state transitions. Computed properties run on every read; caching requires dependency and invalidation rules.
A read-only property does not freeze other attributes or nested objects. Type hints do not execute validation. Test accepted updates and unchanged state after rejection; use a method when several fields must change together.
Put it into practice
Implement the exercise in a full-screen workspace, then run its tests.