Part 3 (final) of a 3-part series. Real code from my Python learning journey, written for beginners.
The Lesson That Made Everything Click
Copying the same code over and over is painful. Functions fixed that for me. A function packages code under a name, so you write it once and reuse it forever. In this final part you will learn functions and arguments, then use them to build two real projects: a shopping receipt generator and a mini bank with a login system.
You do not need Parts 1 and 2 to follow along. The projects use input(), if/else, and while loops, and I explain each part as we go. For deeper detail, Part 1 covers variables and operators, and Part 2 covers loops.
This part covers:
- Defining functions
- Positional, default, and keyword arguments
- return and function pipelines
- Project 1: Shopping Receipt Generator
- Project 2: Mini Bank with Login
1. Functions: Reusable Blocks of Code
My first function:
def describe_student(name, city, track):
print(f"{name} is from {city}, studying {track}")
describe_student("Bob", "Nairobi", "Data Science")
# Output: Bob is from Nairobi, studying Data Science
def tells Python "I am defining a function". The parameters (name, city, track) are placeholders that wait to receive values. The indented block is the body, the code that runs on every call. The last line is the call: it hands the function three values.
Mental model: parameters are the box, arguments are what you put inside. Parameters live in the definition, arguments live in the call.
KEY IDEA
Same function, different inputs, reusable forever. That is the whole point.
2. Three Ways to Pass Arguments
Positional Arguments: Order Matters
def sum_numbers(num1, num2):
return num1 + num2
print(sum_numbers(8, 7)) # 15
The first value goes to the first parameter, the second to the second. Swapping works for addition, but in general order can completely change meaning: "Bob", "Nairobi", "Data Science" in the wrong order describes a different person.
Default Arguments: Flexible Calls
def sum_numbers(num1=8, num2=7):
return num1 + num2
print(sum_numbers()) # 15, uses the defaults
print(sum_numbers(20, 30)) # 50, overrides both
Defaults give the function fallback values. Call it with nothing and it uses 8 and 7; pass values and they override. The receipt project uses this exact idea with discount=0.10 and vat=0.16.
Keyword Arguments: Order Does Not Matter
def sum_numbers(num1=8, num2=7):
return num1 + num2
print(sum_numbers(num2=30, num1=20)) # 50, order swapped and it still works
Naming each value means order no longer matters: num2=30 finds num2, num1=20 finds num1. Keyword arguments are the most readable style, like reading a sentence.
QUICK CHECK
Positional arguments must come before keyword arguments.
sum_numbers(num1=20, 30)raises a SyntaxError.
Why return Changes Everything
These functions use return, not print. print shows a value on the screen and forgets it; return hands the value back so other code can use it. You cannot do math with what print displays, but you can with what return gives you.
def sum_numbers(num1=8, num2=7):
return num1 + num2
result = sum_numbers(20, 30)
print(result + 100) # 150, because sum_numbers returned 50
Passing returned values from one function to the next is the foundation of the receipt project.
REMEMBER
print displays. return delivers. That difference makes functions useful beyond the screen.
3. Project 1: Shopping Receipt Generator
This is the mini project I am most proud of. Three small functions, each doing one job, passing values down a pipeline. try/except catches bad input so a typo does not crash the program:
def generate_receipt():
items_list = []
total = 0
while True:
name = input("Enter item name (or 'quit'): ").strip()
if name.lower() == "quit":
break
while True: # keep asking until the price is valid
price_input = input(f"Enter price for '{name}': ").strip()
try:
amount = int(price_input)
break
except ValueError:
print("Invalid price! Please enter a whole number.")
items_list.append((name, amount))
total += amount
print("ITEMS PURCHASED:")
for name, price in items_list:
print(f"{name:<35} Ksh {price:>6}")
print(f"Subtotal is: Ksh {total:>6}")
return total
def calc_discount(subtotal_amount, discount=0.10):
discount_amount = subtotal_amount * discount
new_total = subtotal_amount - discount_amount
print(f"Discount (10% Off): - Ksh {discount_amount:>6.1f}")
return new_total
def calc_vat(discounted_amount, vat=0.16):
vat_amount = discounted_amount * vat
final_amount = discounted_amount + vat_amount
print(f"VAT (16%): + Ksh {vat_amount:>6.1f}")
print(f"YOUR TOTAL AMOUNT IS: Ksh {final_amount:>6.1f}")
current_subtotal = generate_receipt()
if current_subtotal > 0:
current_discounted = calc_discount(current_subtotal)
calc_vat(current_discounted)
else:
print("No items entered. Receipt cancelled.")
Walk through it in four stages:
- Collect. The while True loop asks for item names until "quit". The inner loop validates the price: try attempts int(), and except catches the ValueError when the user types letters, printing a friendly message instead of crashing. Valid entries are stored as tuples in items_list and added to total.
- Print. The for loop unpacks each tuple. f"{name:<35}" left-aligns the name, f"{price:>6}" right-aligns the price, so the columns line up.
- Return. The function ends with return total, sending the value back to the caller.
- Pipeline. The main code stores the returned subtotal, passes it to calc_discount, and passes that result to calc_vat. Each function does one job and hands the result to the next.
WHY IT WORKS
Collect, subtotal, discount, VAT, total. Four steps, four clean functions. If a bug appears, you know exactly which function to look at.
Mini-challenge: add a loyalty bonus
MINI-CHALLENGE
Add a 5% loyalty bonus on the final total. One more function, or one extra line in calc_vat. Your choice.
How to try it: solve it on your own first, then compare with the solution below.
Solution (no peeking before you try):
def add_loyalty_bonus(amount, bonus=0.05):
bonus_amount = amount * bonus
print(f"Loyalty Bonus (5%): - Ksh {bonus_amount:>6.1f}")
return amount - bonus_amount
# inside calc_vat, at the end:
add_loyalty_bonus(final_amount)
Same patterns as before: a default argument, a calculation, a formatted print, a return. New features become copy-and-adapt, not starting from scratch.
4. Project 2: Mini Bank with Login
Everything from the whole series comes together here: variables, operators, if/else, while loops, and break.
correct_username = "admin"
correct_pin = "1234"
balance = 10000
max_attempts = 3
attempts = 0
logged_in = False
# Login gate, a while loop from Part 2
while attempts < max_attempts:
print("\n=== Mini Bank ===")
username = input("Username: ")
pin = input("PIN: ")
if username == correct_username and pin == correct_pin:
print(f"\nWelcome back, {username}!")
logged_in = True
break
else:
attempts += 1
remaining = max_attempts - attempts
if remaining > 0:
print(f"Incorrect username or PIN. {remaining} attempts remaining")
else:
print("Too many failed attempts. Account locked.")
# Main menu, only if login succeeded
if logged_in:
while True:
print("\n=== Mini Bank ===")
print("1. Check balance")
print("2. Deposit")
print("3. Withdraw")
print("4. Exit")
choice = input("Choose Option: ")
if choice == "1":
print(f"Balance: Ksh {balance}")
elif choice == "2":
amount = float(input("Deposit Amount: "))
balance += amount # assignment operator from Part 1
print(f"Deposited Ksh {amount:,.1f} New balance: Ksh {balance:,.1f}")
elif choice == "3":
amount = float(input("Withdraw Amount: "))
if amount > balance:
print("Insufficient funds")
else:
balance -= amount
print(f"Withdrawn {amount:,.1f} New balance: Ksh {balance:,.1f}")
elif choice == "4":
print("Good Bye")
break
Two sections, two loops:
- The login gate. While attempts is under 3, it asks for credentials and checks both with the and operator from Part 1. A correct login sets logged_in and breaks immediately. A wrong login adds to the counter and either warns or locks the account. This is the Part 2 mini-challenge solution, now in a real project.
- The menu loop. Only if logged_in is True does the menu run. Each choice routes through if/elif/else: deposit uses +=, withdraw uses -= with a nested if for insufficient funds, and exit breaks the loop to end the program.
Spot the series in this project:
- The and operator in the login check, from Part 1
- The while loop with break in login and menu, from Part 2
- The += and -= assignment operators, from Part 1
- The nested if/else for the withdraw check, from Part 2
- f-string formatting with :,.1f, from Part 1
REAL-WORLD CONNECTION
A login gate with limited attempts and a menu loop are two of the most common features in banking apps. You built a real product pattern.
Series Wrap-Up
I started with print("hello world"). Three articles later I have a mini bank that locks itself after three wrong PINs. That is what learning by building looks like.
My honest advice:
- Type the code yourself. Copying builds no muscle memory.
- Break things on purpose. Change values, remove a continue, see what happens.
- Build tiny projects. A receipt, a ticket, a bank. Small wins keep you going.
- Do not fear errors. Mine said "Insufficient funds" for weeks. Errors are just the program talking to you.
The Full Series
- Part 1: Python Basics for Beginners: Variables and Operators
- Part 2: Python Loops and Control Flow for Beginners
- Part 3: Python Functions and Mini Projects for Beginners (you are here)
Your Turn
Which project will you build first, the receipt or the bank? Share your learning tips in the comments. Beginners learn best from each other. Bookmark this series so you can finish it whenever you are ready.
Happy coding.



Top comments (0)