Learning Goals
3 minBy the end of this lesson you can:
- Explain the difference between
printinside a function andreturn. - Store a returned value in a variable, or use the call directly inside an expression.
- Say what a function returns when it has no
returnat all. - Use an early
return, and return two values at once.
Warm-Up · Predict the Output
5 minTwo functions that look nearly identical. Write down what you think each print at the bottom shows, before you run it.
def double_a(n): print(n * 2) def double_b(n): return n * 2 x = double_a(5) y = double_b(5) print("x is", x) print("y is", y)
What actually happens
10 x is None y is 10
The 10 on the first line came from inside double_a — it printed, then handed back nothing. double_b printed nothing at all, but gave the 10 to y, which is why it appears last.
New Concept · Show It, or Give It Back
12 minprint puts characters on the screen for a human to read. return hands a value back to the code that made the call. They are not alternatives — they do completely different jobs.
def total_price(price, qty): """Give back the cost of qty items.""" return price * qty lunch = total_price(4.50, 2) # lunch is now 9.0 drinks = total_price(2.00, 3) # drinks is now 6.0 print("Bill: $", lunch + drinks)
Because total_price(...) is a value once it returns, you can put it anywhere a value belongs:
print(total_price(4.50, 2)) # straight into print grand = total_price(4.50, 2) + 3.00 # into a sum if total_price(4.50, 2) > 10: # into a condition print("Over budget!")
A function with no return still hands back a value: None, Python's word for "nothing here". That is why x was None in the warm-up. It is not an error, and it will not crash — until you try to do arithmetic with it, and get TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'. That message almost always means a function you expected to return forgot to.
return stops the function immediately. Nothing after it runs. That is a feature, not a limitation — it lets you deal with the awkward case first and leave:
def grade(mark): """Return the letter grade for a mark out of 100.""" if mark < 0 or mark > 100: return "Invalid" # leave straight away if mark >= 80: return "A" if mark >= 65: return "B" if mark >= 50: return "C" return "F"
No else anywhere, and it still cannot fall through: once one return runs, the function is over.
Two answers at once. Separate them with a comma, and unpack them at the call:
def high_and_low(marks): """Return the best and worst mark in one go.""" return max(marks), min(marks) best, worst = high_and_low([72, 55, 91, 68]) print(best, worst) # 91 55
Worked Example · A Bill, Built From Small Functions
12 minEach function answers one question, and the last few lines put the answers together. Type it and run it.
def subtotal(price, qty): """Cost before any discount or tax.""" return price * qty def discount(amount, is_student): """Return the discount in dollar: 10% for students, nothing otherwise.""" if is_student: return amount * 0.10 return 0 def service_charge(amount): """Return the 6% service charge.""" return amount * 0.06 def final_total(price, qty, is_student): """Put the three together and return what the customer pays.""" base = subtotal(price, qty) after_discount = base - discount(base, is_student) return after_discount + service_charge(after_discount) bill = final_total(4.50, 2, True) print(f"Total to pay: $ {bill:.2f}")
Output
Total to pay: $8.59
Look at what final_total does: it calls three other functions and returns the result of combining them. Not one of the four contains a print — the only printing happens on the last line, where a human is actually being shown something.
A function that returns can be reused anywhere: on screen, saved to a file, added to a running total, tested against an expected answer. A function that prints can only ever do one thing — talk to whoever is watching. Calculate in one place, display in another, and the same code serves a receipt, a report and a test.
Prove it to yourself: add this line at the bottom, with no change to any function above.
print("Ten of them would be $", final_total(4.50, 10, False))
Try It Yourself
13 minRewrite show_fahrenheit from FUN-02 as to_fahrenheit(celsius), which returns the number instead of printing it. Then use it twice in one line: print the temperature, and print whether it is above 90°F.
Hint
def to_fahrenheit(celsius): """Return the temperature in Fahrenheit.""" return celsius * 9 / 5 + 32 print(to_fahrenheit(35)) if to_fahrenheit(35) > 90: print("Panas!")
Write check_mark(mark) that returns two things: the word "Pass" or "Fail", and how far the mark is from the 50 pass mark. Print a sentence for four different marks.
Hint
def check_mark(mark): """Return (verdict, distance from the pass mark).""" if mark >= 50: return "Pass", mark - 50 return "Fail", 50 - mark verdict, gap = check_mark(43) print(f"{verdict} - {gap} marks from the line")
Use abs() if you would rather write it as one return.
Mini-Challenge 🔥 · Debug: The Total That Says None
8 minZoe's program prints the right numbers on the way through, then crashes at the end with TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'.
def item_cost(price, qty):
cost = price * qty
print(f"{qty} x $ {price:.2f} = $ {cost:.2f}")
rice = item_cost(4.50, 2)
tea = item_cost(2.00, 3)
print("Bill: $", rice + tea)Explain, in one sentence each: why the first two lines of output look perfectly correct, and what rice actually contains.
Answer
item_cost prints, but never returns. The printing is real work and it happens, which is why the output looks right — but the function hands back None, so rice and tea are both None, and adding two Nones is the error.
Fix: add return cost at the end. Keep the print if you want the running commentary, or — better — move it out to the caller so the function does one job:
def item_cost(price, qty): """Return the cost of qty items.""" return price * qty rice = item_cost(4.50, 2) print(f"2 x $4.50 = $ {rice:.2f}")
Rule of thumb: if you ever write x = some_function(...) and get None, go and look for the missing return. It is nearly always that.
Recap
3 minprint shows a human; return hands a value back to the code. A call that returns is a value, so it can go into a variable, a sum, or a condition. A function without return gives back None, which is where mysterious TypeErrors come from. return ends the function on the spot, which makes early returns tidy, and a comma lets one function return two answers.
Vocabulary Card
- return
- Hands a value back to the caller and ends the function immediately.
- None
- Python's "nothing" value. What every function without a
returngives back. - early return
- Dealing with a special case at the top and leaving, so the rest of the function stays simple.
- unpacking
best, worst = high_and_low(marks)— splitting two returned values into two names.
Homework
4 minWrite marks_report.py. It needs four functions that return and never print: an average of a list of marks, the highest and lowest together, a letter grade for one mark, and a pass/fail verdict.
Then, at the bottom of the file only, print a report for a class of at least six marks. Count your print statements — they should all be below the functions.
Hint: sum(marks) / len(marks) is the average. Guard against an empty list with an early return 0.
Sample · marks_report.py
# marks_report.py - the functions calculate, the bottom of the file talks def average(marks): """Return the mean mark, or 0 for an empty list.""" if len(marks) == 0: return 0 return sum(marks) / len(marks) def high_and_low(marks): """Return the best and worst mark.""" return max(marks), min(marks) def grade(mark): """Return the letter grade for one mark.""" if mark >= 80: return "A" if mark >= 65: return "B" if mark >= 50: return "C" return "F" def verdict(mark): """Return Pass or Fail for one mark.""" if mark >= 50: return "Pass" return "Fail" marks = [72, 55, 91, 68, 43, 50] best, worst = high_and_low(marks) print(f"Class average : {average(marks):.1f}") print(f"Best / worst : {best} / {worst}") print("-" * 30) for mark in marks: print(f" {mark:>3} {grade(mark)} {verdict(mark)}")
The check that matters: could you delete every print and still use these four functions to save a report to a file? If yes, the split is right. If a function had to print to be useful, it is doing two jobs.