Learning LibraryCore Coding LibraryTeens

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

1The basics

Two functions that look almost identical. One prints the margin; the other returns it. Watch what the caller receives:

python
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)
Run it. Printed, then returned:
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.

2A step further

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:

python
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))
Run it. The same function, with return in two places:
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.

3At Evergreen

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:

python
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))
Run it. Functions feeding functions:
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.

The same idea, everywhere

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.

Want the simpler version? Read the Kids version →