Return values in Python: how a function hands back its answer
A function that prints its answer has told you something. A function that returns its answer has told your program something, and that's the one you can build on.
The big idea
return ends a function immediately and sends a value back to the caller; a function with no return gives back None.
See it in code
Two functions that look almost identical. One prints the margin; the other returns it. Watch what the caller receives:
def show_margin(price, cost):
print((price - cost) / price)
def margin(price, cost):
return (price - cost) / price
result = show_margin(38, 14)
print("show_margin gave back:", result)
result = margin(38, 14)
print("margin gave back:", result)0.631578947368421 show_margin gave back: None margin gave back: 0.631578947368421
show_margin displayed the number and then handed back None. You could read it, but no other line of code could use it.
return exits the function on the spot, even from inside a loop. One level of indentation decides whether you get a year of data or a single month:
def first_month_only(start_units, growth, months):
units = start_units
result = []
for month in range(months):
result.append(units)
units = round(units * (1 + growth))
return result
def monthly_units(start_units, growth, months):
units = start_units
result = []
for month in range(months):
result.append(units)
units = round(units * (1 + growth))
return result
print("Indented too far:", first_month_only(300, 0.15, 4))
print("After the loop:", monthly_units(300, 0.15, 4))Indented too far: [300] After the loop: [300, 345, 397, 457]
In the first version return sits inside the loop, so the function ends on the very first pass. Moved out one level, it waits until the loop has finished.
Because calls evaluate to values, one function's result can be another's argument. Here is the margin that's left after a 30% sale:
def margin(price, cost):
return (price - cost) / price
def sale_price(price, discount=0.3):
return round(price * (1 - discount), 2)
def is_profitable(price, cost):
return price > cost
print(f"Full-price margin: {margin(38, 14):.1%}")
print(f"Margin at the sale price: {margin(sale_price(38), 14):.1%}")
print("Still profitable at $12?", is_profitable(12, 14))Full-price margin: 63.2% Margin at the sale price: 47.4% Still profitable at $12? False
A 30% discount doesn't leave a 33% margin. The price fell but the cost didn't, so the margin is 47.4%. A function can also return a boolean, as is_profitable does.
Returning is what makes code composable. A function that returns a cleaned string, a list of signals, or a hash can be dropped into any larger expression that needs one.
Try it yourself
Rewrite show_margin so it returns the value, then print margin(38, 14) - margin(sale_price(38), 14) to see the margin a sale gives up. Could you have done that with the printing version?
The common mistake
Forgetting return and then using the result. profit = show_margin(38, 14) * 500 raises a TypeError, because None * 500 isn't arithmetic. If a result is needed later, return it.
What it unlocks
Return values are the output side of functions. They also explain why a variable set inside a function doesn't change one outside it, which is scope.