Functions: Beginner
The def statement
The first line is the header: the keyword def, the function's name, a pair of parentheses and a colon. Every indented line under it belongs to the function and is called its body. The body ends where the indentation ends, the same way the body of an if or a for does.
- ▸
deftells Python that a function definition starts here - ▸
print_receipt_headeris the name the program will call later - ▸
()holds the parameter names, empty when the function needs no input - ▸
:closes the header, and the indented body follows
Running the def line creates the function and attaches the body to the name, and that is all it does: nothing inside the body runs. The body runs when the program calls the function, which means writing its name followed by parentheses. This program prints before the definition, prints after it, and then calls the function twice:
Define first, call second
Python runs a program from the top down, and a function's name does not exist until its def line has run. Calling the name on a line above the definition fails with a NameError, the same error a misspelled variable raises:
Running it prints nothing. The call on line 1 runs before the definition has had a chance to, and Python stops with NameError: name 'print_total' is not defined. The message gives the type of failure and the name Python could not find. Moving the call below the def block fixes it, because by then the name exists.
Naming a function
A function name follows the rules for variable names: letters, digits and underscores, no spaces, no leading digit. Because a function does something, its name usually starts with a verb, so a reader of print_receipt_header() knows what will happen without opening the body.
- Start with a verb: print_receipt, check_balance, send_alert
- Use lowercase words joined by underscores
- Say what the body does, in full words
- Reuse a built-in name such as print or range for your own function
- Abbreviate to the point of guessing: prh()
- Give 2 functions names that differ by one letter
The fare formula as a function
The function below holds the Uber formula once. The names in its parentheses, miles and minutes, stand in for the numbers each trip supplies, and the next section covers how a call fills them. Step through the run and watch the body execute 3 times with 3 sets of numbers:
1def print_fare(miles, minutes):2 cents = 250 + miles * 175 + minutes * 353 print("Fare in cents:", cents)45print_fare(3, 12)6print_fare(8, 25)7print_fare(1, 6)
Each call runs the body once with its own miles and minutes.
How many lines does this program print?
A working function needs both halves: the def keyword that opens the header, and a call that writes the name followed by parentheses. Leave out def and Python rejects the header line before anything runs. Write the name without its parentheses and the body stays silent, because the parentheses are what tell Python to run it. The call can sit anywhere below the definition, as many times as the program needs.
In the header def describe_trip(miles, minutes), the names miles and minutes are parameters: variables the body uses for values that do not exist yet. In the call describe_trip(8, 25), the values 8 and 25 are arguments: the data that fills those variables for this one run of the body. A parameter is a slot; an argument is what goes into it.
| Call | miles holds | minutes holds | Prints |
|---|---|---|---|
| describe_trip(8, 25) | 8 | 25 | Miles: 8 then Minutes: 25 |
| describe_trip(1, 6) | 1 | 6 | Miles: 1 then Minutes: 6 |
| describe_trip(40, 90) | 40 | 90 | Miles: 40 then Minutes: 90 |
Positional arguments are matched by order
Keyword arguments are matched by name
Writing parameter=value in a call names the slot, so the position no longer matters. Both calls below produce the same line, and the second one mixes the 2 styles:
One rule governs mixing: every positional argument comes before every keyword argument. The call describe_trip(miles=8, 25) is refused before the program starts, with the message positional argument follows keyword argument, because Python has no way to know which slot the bare 25 belongs in. Keyword arguments earn their keystrokes when a function takes several numbers of the same type: describe_trip(miles=8, minutes=25) reads as a sentence, while describe_trip(8, 25) asks the reader to remember the header.
The count has to match
Running it raises TypeError: describe_trip() missing 1 required positional argument: 'minutes'. The message names the function and the empty slot. Supplying too many arguments fails the same way, with describe_trip() takes 2 positional arguments but 3 were given. Either way the body never starts, so a print inside it leaves no trace in the output.
describe_trip(8, 25) finishes, the name miles is gone, and using it on the next line raises a NameError. The rules behind this, called scope, have a lesson of their own later in the track.Every call fills the slots again
A call inside a loop runs the body once per pass, and each pass delivers new arguments. Surge pricing multiplies a base fare by a level from 1 to 3; the loop below passes the same cents each time and a different multiplier, and the final call fills both slots by keyword:
1def print_surge_fare(cents, multiplier):2 total = cents * multiplier3 print("Base", cents, "x", multiplier, "=", total)45for level in range(1, 4):6 print_surge_fare(1000, level)78print_surge_fare(multiplier=2, cents=635)
Three positional calls from the loop, then one call that fills both slots by keyword.
On each pass multiplier takes the loop's current value, and total is computed fresh from that pass's arguments. The keyword call at the end lists multiplier before cents, and the output shows that the order in the call made no difference.
What does the second call print?
print_fare from the first section shows a number on the screen and that is the end of it: the program cannot add that fare to another, compare it with a limit or store it for a receipt. The return statement hands a value back to the line that made the call, where it can be used like any other value. The 2 functions below compute the same number and differ only in what they do with it:
The call show_fare(2) printed 600 as a side effect but handed nothing back, so a holds None, the value Python uses for the absence of a value. Any function that reaches the end of its body without meeting a return hands back None. The call fare(2) printed nothing and handed back 600, which the assignment stored in b.
- Sends text to the screen for a person to read
- The calling line receives None
- Belongs where a person needs to see the value: a receipt line, a progress report, a final answer
- Sends the value to the calling line for the program to use
- The calling line receives the value
- Belongs in every function whose result feeds another step
Using the returned value
1def fare_cents(miles, minutes):2 return 250 + miles * 175 + minutes * 3534short_trip = fare_cents(1, 6)5airport_run = fare_cents(8, 25)6print("Short trip:", short_trip)7print("Airport run:", airport_run)89both = short_trip + airport_run10print("Both trips:", both)1112surge_total = fare_cents(3, 12) * 213print("Surge trip:", surge_total)
A returned value is stored, added and multiplied like any other number.
fare_cents(3, 12) evaluates to 1195 inside the last expression, and the multiplication by 2 happens on that number before surge_total is assigned. A call that returns a value is an expression, so it fits anywhere a number fits.
return ends the function at once
When Python reaches a return, the function stops immediately, even if more lines follow. A function with several return statements inside if blocks therefore hands back the value of the first condition that holds. Step through the fare bands and count how many conditions each call tests:
1def fare_band(cents):2 if cents < 0 or cents > 50000:3 return "Invalid fare"4 if cents >= 4000:5 return "Premium"6 if cents >= 2000:7 return "Long"8 if cents >= 1000:9 return "Standard"10 if cents >= 500:11 return "Short"12 return "Minimum"1314print(fare_band(4200))15print(fare_band(2525))16print(fare_band(1195))17print(fare_band(300))18print(fare_band(-50))19print(fare_band(60000))
Each call stops at the first band its fare meets; 300 falls through every check to the last return.
The fare 4200 passes the range check, meets cents >= 4000 and leaves with Premium without touching the rest; 300 fails every band check and falls through to the final return. The order of the checks carries the logic: because 4200 is also at least 2000, swapping the Premium and Long checks would label it Long. The range check sits first for the same reason, so that 60000 is rejected before cents >= 4000 can claim it.
The print after the return is unreachable: no call can ever execute it, and leaving it there misleads the next reader into expecting output that never comes.
When None reaches arithmetic
The program prints Total: 1195, then stops with TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'. result holds None, and + has no meaning between NoneType and int. The fix is one line: replace the print with return cents, and let the caller print when it wants to.
What does this program print?
The value after return can be any expression, from a single variable to a whole calculation. Python computes the expression first, using the parameter values from this call, and then hands back the result: return miles * 175 with miles holding 4 hands back 700. The caller never sees the expression, only the number it produced.
| Step | Expression being evaluated | Value |
|---|---|---|
| 1 | subtotal_cents(3, 1299) | 3897 |
| 2 | with_tax(3897) | 4208 |
| 3 | with_delivery(4208) | 4707 |
| 4 | print(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:
1def subtotal_cents(qty, unit_cents):2 return qty * unit_cents34def with_tax(cents):5 return cents + cents * 8 // 10067def with_delivery(cents):8 return cents + 499910order = subtotal_cents(3, 1299)11taxed = with_tax(order)12total = with_delivery(taxed)13print("Step by step:", total)1415chained = with_delivery(with_tax(subtotal_cents(3, 1299)))16print("Chained:", chained)
Stored steps and a nested call reach the same 4707; the nested call evaluates from the inside out.
A function can call another function
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:
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?
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.
The 3 double quotes on each end let the string span several lines, which a single quote on each end does not allow. The first line is a one-sentence summary that starts with a verb and ends with a period; a blank line separates it from the details, which name each parameter and say what the return value means, unit included. A closing """ on its own line marks the end.
Python keeps the docstring
A comment is dropped when the program runs. A docstring stays attached to the function, and the built-in help function prints it along with the header. Editors show that same stored docstring as a description when a cursor hovers over a function name:
help reports the function's name, its parameters in order, and the docstring under them, with no need to open the body. A one-line docstring like this one fits a function whose parameters explain themselves.
Docstrings and comments
- First statement in the body, in triple quotes
- Says what the function does, for the people who call it
- Stored with the function and shown by help()
- Any line, starting with #
- Says why a line is written the way it is, for the people editing the body
- Dropped when the program runs
Position is the whole test. A triple-quoted string that comes after another statement in the body is an ordinary expression that Python evaluates and discards, so help shows no description for it. The function below contains a string that looks like a docstring:
What does help(tip_cents) show for this function?
- Put the docstring on the line right after the def header
- Start with a verb: Return, Print, Check
- Name the unit of every number: cents, miles, minutes
- Update the docstring in the same edit that changes the behavior
- Restate the code line by line
- Describe a parameter the function no longer has
- Use a comment above def for what the function does
- Write a docstring longer than the body of a 1-line function
Capstone: a documented pricing module
The first trip is 950 cents of fare plus a 142 cent tip, the second 1650 plus 247, the third 2350 plus 352, and the 3 totals sum to 5691. The airport run at 8 miles and 25 minutes is 2525 plus a 378 cent tip. Every number came from one copy of each formula, and help(trip_total) would tell a teammate what the function does without reading a line of its body.
> Uber's pricing team raises the per-minute rate for one city from 35 to 40 cents. The change is a one-number edit inside fare_cents, because the rider quote, the receipt and the driver earnings screen all call that one function with their own miles and minutes instead of carrying copies of the formula. The returned cents flow into tip_cents and trip_total, the weekly earnings loop picks up the new rate on its next pass, and the engineer on call reads the docstring to confirm the function still expects whole miles and whole minutes before approving the change.
Write a formula once with def, feed it arguments, return the result, chain the calls and document the whole thing.
- Category
- Python
- Difficulty
- beginner
- Duration
- 38 minutes
- Challenges
- 18 hands-on challenges
Topics covered: Defining with def, Parameters and Arguments, Return Values, Calling and Storing, Docstrings
Lesson Sections
- Defining with def (concepts: Functions)
Uber prices a trip in cents: 250 for the pickup, 175 for every mile driven and 35 for every minute in the car. A program that quotes 3 trips with this formula writes the arithmetic out 3 times: Every pair of lines repeats the same 3 multiplications with different numbers. When the pickup charge rises to 300, each copy needs its own edit, and a copy that goes unedited quotes the old price. A function stores the arithmetic once under a name, and every place that needs a quote calls that name. The
- Parameters and Arguments (concepts: Flexible Arguments)
Positional arguments are matched by order With no other information, Python matches arguments to parameters by position: the first argument goes into the first parameter, the second into the second. Python checks nothing about meaning, so swapping the 2 numbers in the call swaps what the function believes about the trip: The second call describes a 25 mile, 8 minute trip, which no car in a city can drive, and Python reports it without complaint. The order of arguments is the caller's responsibil
- Return Values (concepts: Functions)
Using the returned value A returned value can be stored in a variable, added to another, or multiplied right there in the calling expression. Two trips are priced, summed and surged in the run below: return ends the function at once When None reaches arithmetic The mistake that follows from confusing the 2 statements is using the result of a printing function in a calculation. The program below prints the total, then tries to add 100 to what the call handed back:
- Calling and Storing (concepts: 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: Both routes reach 4707. In the nested call, Python evaluates the innermost call first, because the outer calls need its result as their argument: A function can ca
- Docstrings (concepts: Functions)
A function at Uber is read far more often than it is written: by the engineer on call at 3 a.m., by the teammate who wants to reuse it, by the person deciding whether a change is safe. A docstring is a string placed as the first line of the body that explains what the function does, what it expects and what it hands back. Python stores it with the function, which no comment can claim: Python keeps the docstring Docstrings and comments Capstone: a documented pricing module The program below bring