Python Tutorial / Control Object Updates with Methods and Properties
Methods express object behavior, while property preserves attribute-access syntax while applying read or write rules. A property can enforce a consistent assignment rule both during initialization and during later updates.
Keep the public value separate from internal storage
The public amount interface stores its value internally as _amount. A leading underscore signals implementation detail by convention; it does not provide a security barrier or protect secrets from outside access.
Initializing through self.amount invokes the same setter used for later assignments. This keeps the initial and subsequent validation paths consistent.
For the relevant APIs and platform requirements, see Python: functions.
Object initialization and runtime validation build on the rules demonstrated in Group State and Behavior with Classes and Instances and Describe Function Inputs and Results with Type Hints.
Run validated assignments and a rejected update
Save the complete code below as properties.py in a separate practice folder, using UTF-8. Run python properties.py in a terminal with your project virtual environment selected. Outside a virtual environment on macOS or Ubuntu, use python3 when that is your interpreter command.
class Expense:
def __init__(self, amount):
self.amount = amount
@property
def amount(self):
return self._amount
@amount.setter
def amount(self, value):
if type(value) is not int or value < 0:
raise ValueError("amount must be a nonnegative integer")
self._amount = value
def add_fee(self, fee):
self.amount = self.amount + fee
item = Expense(8000)
item.add_fee(500)
print(item.amount)
try:
item.amount = -1
except ValueError:
print("rejected")
print(item.amount)
Expected output:
8500 rejected 8500
Avoid recursion and partial updates
Inside the setter, assigning self.amount = value calls the setter again and causes recursion. Write the validated value to _amount instead. Check the rule before mutation so that a rejected value leaves the previous valid state intact.
Avoid hiding network requests or large file operations behind what looks like a simple attribute read. An explicit method often better communicates a potentially expensive or failing operation.
Reading obj.amount invokes the getter; assigning obj.amount = value invokes the setter. @property defines this attribute access rule, so callers do not add parentheses. Validate first, then store in a separate backing attribute such as _amount. Assigning self.amount inside its own setter recursively calls that setter.
| Item | Behavior |
|---|---|
| obj.amount | Calls getter |
| obj.amount = 100 | Setter validates, then stores _amount |
Choose read-only access and test the policy
A property without a setter rejects ordinary assignment through that property. This does not make the entire object deeply immutable. Choose a property for value access and an explicit method for a distinct operation.
- Assign True and then 3.5 and check that the integer-only rule rejects both.
- After each rejected assignment, verify that the amount remains 8500.
- Decide whether a negative fee should be allowed when the final amount remains nonnegative; that is an additional method policy.
A validated final amount does not automatically validate every input’s independent business meaning. Keep the amount invariant and fee policy explicit, and test both if both matter.




