Calling and Storing

Concepts covered: Functions

An Uber Eats order passes through 3 pricing steps: the items are multiplied out, tax is added to that subtotal, and a delivery fee is added to the taxed amount. Each step is a function, and the result of one is the argument of the next. The program runs the chain twice, once storing every intermediate value and once nesting the calls:

def subtotal_cents(qty, unit_cents):
    return qty * unit_cents

def with_tax(cents):
    return cents + cents * 8 // 100

def with_delivery(cents):
    return cents + 499

order = subtotal_cents(3, 1299)
print(order)
taxed = with_tax(order)
print(taxed)
print(with_delivery(taxed))
print(with_delivery(with_tax(subtotal_cents(3, 1299))))

Output:

3897
4208
4707
4707

Both routes reach 4707. In the nested call, Python evaluates the innermost call first, because the outer calls need its result as their argument:

StepExpression being evaluatedValue
1subtotal_cents(3, 1299)3897
2with_tax(3897)4208
3with_delivery(4208)4707
4print(4707)prints 4707

Reading a nested call means reading from the inside out, the opposite of left to right. Watch the 2 versions run one after the other and note that the chained line produces the same 4707 without ever naming order or taxed:

def subtotal_cents(qty, unit_cents):
    return qty * unit_cents

def with_tax(cents):
    return cents + cents * 8 // 100

def with_delivery(cents):
    return cents + 499

order = subtotal_cents(3, 1299)
taxed = with_tax(order)
total = with_delivery(taxed)
print("Step by step:", total)

chained = with_delivery(with_tax(subtotal_cents(3, 1299)))
print("Chained:", chained)
Step by step: 4707
Chained: 4707

A function can call another function

A call can sit inside another function's body as well as at the top level of the program. A body can call any function that has been defined by the time the body runs, which lets one function wrap the whole chain:

def with_tax(cents):
    return cents + cents * 8 // 100

def order_total(qty, unit_cents):
    taxed = with_tax(qty * unit_cents)
    return taxed + 499

print(order_total(3, 1299))
print(order_total(1, 899))

Output:

4707
1469

order_total calls with_tax in its own body, waits for the answer, then adds the delivery fee. The second order, 1 item at 899 cents, pays 71 cents of tax (8 percent of 899, rounded down by //) and 499 for delivery.

Calls inside loops and conditions

A returned value is at home anywhere an expression is: added to a running total inside a loop, or compared against a limit in an if. The loop below prices 3 trips of growing length, each lasting 4 minutes per mile, and keeps the sum:

def fare_cents(miles, minutes):
    return 250 + miles * 175 + minutes * 35

total = 0
for miles in range(1, 4):
    total = total + fare_cents(miles, miles * 4)
    print("After", miles, "miles:", total)
print("All trips:", total)

Output:

After 1 miles: 565
After 2 miles: 1445
After 3 miles: 2640
All trips: 2640

On each pass the call receives the loop's miles and 4 times that for minutes; the 3 fares are 565, 880 and 1195. A call in a condition works the same way: if fare_cents(8, 25) > 2000: computes 2525 first, and only then does the comparison choose the branch.

In what order do the 3 functions run, and what is printed?

def double(n):
    return n * 2

def add_one(n):
    return n + 1

def square(n):
    return n * n

print(double(add_one(square(3))))

To write a nested call yourself, list the steps in the order they must happen, then build the line from the inside out: the first step is the innermost call, and each later step wraps the one before it. In the question above, squaring came first, so square(3) sat at the center and double wrapped everything else.

About this lesson

This section is part of the Functions: Beginner lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.