Most design rounds start the same way: turn a small domain into a class whose state can never become invalid. Here the rule is simple: a balance never goes negative, and a failed operation changes nothing.
Implement class Account:
Account(owner, balance=0)stores the owner's name and a starting balance (a non-negative integer, in cents).deposit(amount)addsamountto the balance.withdraw(amount)subtractsamountfrom the balance.transfer(other, amount)movesamountfrom this account into theAccountother.balance()returns the current balance.history()returns a list of this account's successful operations, oldest first, as(kind, amount)tuples.kindis"deposit","withdraw","transfer_out"or"transfer_in".
Validation, all raising ValueError:
amountmust be a positive integer for all three operations (0and negatives are rejected).withdrawandtransferraise if the balance is too small.transferraises ifotheris this same account.- The constructor raises if the starting balance is negative.
When an operation raises, neither account's balance or history may change.
a = Account("ann", 100)
b = Account("bob")
a.deposit(50)
a.transfer(b, 120)
a.balance(), b.balance() # (30, 120)
a.history() # [("deposit", 50), ("transfer_out", 120)]
b.history() # [("transfer_in", 120)]
a.withdraw(31) # ValueError: insufficient funds; a.balance() is still 30
Two classic traps: every account needs its own history list (watch out for mutable default arguments or class-level lists), and history() should return a copy, so a caller can't edit your account's state through it.
Show hint
validate first and mutate last: check every rule at the top of each method, and only then touch the balances and histories.