Welcome back. In the last lesson, you used loops and comprehensions to apply the same transformation to many values: filtering paid transactions, converting cents to dollars, and normalizing text. That works well for a one-off notebook cell. But once a rule is useful more than once, copying its code becomes a maintenance problem.
Functions let you give one focused rule a name, specify its inputs, and receive a result back. This lesson focuses on writing small, single-purpose functions—the kind you will later combine into data-cleaning and machine-learning workflows. You will also document them with concise docstrings so that both you and a future collaborator can understand how to use them.
A function is a named, reusable rule
A function packages a set of instructions under a name. Defining a function does not run its body immediately. It teaches Python what to do when the function is called.
Consider a familiar data transformation:
def cents_to_dollars(cents):
"""Convert an amount in cents to dollars."""
return cents / 100
Read the definition in parts:
| Part | Meaning |
|---|---|
def | Start a function definition. |
cents_to_dollars | The function’s name. Use lowercase words separated by underscores. |
(cents) | Its parameter list: the input name available inside the function. |
: | Ends the function header. |
| Indented lines | The function body, which runs only when called. |
return | Send a value back to the code that called the function. |
Now call it:
dollars = cents_to_dollars(12500)
print(dollars)
Output:
125.0
There are two distinct moments:
- When Python runs the
defcell or line, it creates a function namedcents_to_dollars. - When Python reaches
cents_to_dollars(12500), it runs the body withcentsassigned the value12500.
A brief trace makes the value flow concrete:
| Location | Name | Value |
|---|---|---|
| Calling code | argument | 12500 |
| Inside the function | cents | 12500 |
| Inside the function | returned value | 125.0 |
| Calling code | dollars | 125.0 |
The parameter cents is a local name: it exists for the function’s work and is not a variable you should try to use elsewhere in your notebook. Local names help keep each function self-contained.
In a notebook, run the cell containing the definition before running a cell that calls it. In a script, place function definitions above the code that first calls them.
Python Tutorial for Beginners 8: Functions
Watch Corey Schafer’s “Python Tutorial for Beginners 8: Functions” to see the key distinction between displaying a value and returning it, then to see how call arguments become local parameters.
Watch return values first. Focus on the idea that a function call evaluates to the value it returns, so that value can be stored, printed, or used in another calculation. Then watch parameters. Notice the required input, the local scope of the parameter, and how a call supplies a concrete value.
Parameters are inputs; arguments are supplied values
A parameter is the placeholder name in a function definition. An argument is the actual value supplied when calling it.
def format_customer_label(customer_id, region):
"""Return a customer label containing its region."""
return f"{customer_id} ({region})"
Here, customer_id and region are parameters. This call supplies two arguments:
label = format_customer_label("C101", "north")
print(label)
C101 (north)
By default, Python matches positional arguments to parameters by order:
format_customer_label("C101", "north")
Python assigns "C101" to customer_id and "north" to region.
You can also use keyword arguments, which make the mapping explicit:
format_customer_label(region="north", customer_id="C101")
The result is the same. Keyword arguments are especially helpful when a function has several values of the same general type, such as IDs, dates, thresholds, or numeric settings. At this stage, favor simple functions with a small number of clearly named parameters.
A useful function name and parameter names make its interface readable:
def review_label(score, cutoff):
"""Return a readiness label based on a score and cutoff."""
if score >= cutoff:
return "ready"
return "review"
label = review_label(score=8, cutoff=7)
print(label)
ready
This function has one focused responsibility: given a score and a cutoff, choose a label. It does not print a report, modify a dataset, save a file, and calculate unrelated summaries. Those might be separate responsibilities later in a larger program.
A function is single-purpose when you can describe its job in one precise sentence:
clean_region(region): normalize one region label.cents_to_dollars(cents): convert one monetary amount.is_paid_status(status): decide whether one status means paid.review_label(score, cutoff): assign one score label.
“Single-purpose” does not mean “one line.” A function can use conditions, loops, and intermediate variables when they are all necessary for one coherent job.
Return values make functions useful in data workflows
For data transformations, prefer functions that return a result rather than functions that merely print it.
Compare these two versions:
def show_dollars(cents):
"""Print an amount converted from cents to dollars."""
print(cents / 100)
result = show_dollars(12500)
print(result)
Output:
125.0
None
The first line is printed by the function. But show_dollars() does not use return, so its call evaluates to None. The variable result therefore receives None, not 125.0.
Now use a return value:
def cents_to_dollars(cents):
"""Convert an amount in cents to dollars."""
return cents / 100
result = cents_to_dollars(12500)
print(result)
Output:
125.0
This version is more reusable. The caller can decide what to do with the returned number:
first_amount = cents_to_dollars(12500)
second_amount = cents_to_dollars(8000)
total_amount = first_amount + second_amount
print(total_amount)
205.0
You can also use a returned value directly inside another expression:
total_amount = cents_to_dollars(12500) + cents_to_dollars(8000)
The function’s job is conversion; the calling code’s job is deciding whether to store, display, add, plot, or later model the result.
This distinction becomes important with pandas and scikit-learn. Many operations are most predictable when they accept data and return transformed data, rather than silently changing the input or printing intermediate output.
For example, this function takes one text value and returns a cleaned version:
def clean_region(region):
"""Return a trimmed lowercase region label."""
return region.strip().lower()
You can apply that one-value rule across a list using the comprehension pattern from the previous lesson:
raw_regions = [" North ", "SOUTH", " north"]
clean_regions = [clean_region(region) for region in raw_regions]
print(clean_regions)
['north', 'south', 'north']
The original list remains available unchanged:
print(raw_regions)
[' North ', 'SOUTH', ' north']
That separation makes it easier to inspect what happened and later test whether clean_region() behaves correctly.
A return statement also ends the current function call. In review_label(), only one of the two return statements can run:
def review_label(score, cutoff):
"""Return a readiness label based on a score and cutoff."""
if score >= cutoff:
return "ready"
return "review"
The second return is not “outside” the decision. It handles the case where the if condition was false.
Write docstrings as part of the function contract
A docstring is a string placed as the first statement inside a function. It provides built-in documentation that Python tools and editors can display.
def clean_region(region):
"""Return a trimmed lowercase region label."""
return region.strip().lower()
For the short functions you are writing now, a one-sentence docstring is usually enough. Make it:
- an action-oriented description;
- accurate about the function’s result;
- capitalized and ended with a period;
- placed immediately inside the function body.
A good docstring describes behavior, not a vague intention:
| Less useful | Better |
|---|---|
"""Cleans a region.""" | """Return a trimmed lowercase region label.""" |
"""Does calculation.""" | """Convert an amount in cents to dollars.""" |
"""Checks status.""" | """Return whether a transaction status is paid.""" |
A comment beginning with # can explain a particular implementation detail, but it is not a docstring. Likewise, a triple-quoted string placed after other code is just an unused string, not the function’s documentation.
You can inspect a docstring in a notebook or Python shell with help():
help(clean_region)
You can also access it directly:
print(clean_region.__doc__)
4. More Control Flow Tools — Python 3.14.0 documentation
Read the relevant parts of Python’s official tutorial, “More Control Flow Tools.” It reinforces function definitions, local parameter names, return values, and the standard conventions for concise docstrings.
In Section 4.8, “Defining Functions,” begin with the Fibonacci function example and read through the version that returns a list, stopping before Section 4.9, “More on Defining Functions.” Focus on the difference between a function that prints and one that returns a value, and on why the body is indented. Read the local-name explanation to connect the documentation’s terminology with the parameter and argument distinction used here. Then jump to Section 4.9.7, “Documentation Strings.” Read the docstring conventions. For your current functions, adopt the concise first-line convention; fuller multi-line documentation becomes useful once a function has more complex inputs, outputs, or side effects.
Implementation studio: reusable transaction rules
Spend about 12 minutes in a new notebook named function_practice.ipynb. Type the definitions yourself rather than generating them with an assistant. The goal is not just to obtain an output; it is to practice translating a small contract into valid Python.
Start with this data:
transactions = [
{"transaction_id": "T001", "status": " paid ", "amount_cents": 12500},
{"transaction_id": "T002", "status": "refunded", "amount_cents": 4200},
{"transaction_id": "T003", "status": "PAID", "amount_cents": 8000},
]
Build these three functions, each with a concise docstring:
clean_status(status)
Return a stripped, lowercase status label.
is_paid_status(status)
Return True when a cleaned status is "paid"; otherwise return False.
cents_to_dollars(cents)
Return the monetary amount in dollars.
Keep each definition above the loop that uses it. Then write the following workflow in ordinary Python:
- Create an empty
paid_amountslist. - Loop through
transactions. - Extract and clean each transaction’s
"status". - Keep the transaction only if its cleaned status is paid.
- Convert its
"amount_cents"using your conversion function. - Append the returned dollar amount.
Your final result should be:
[125.0, 80.0]
Once the code runs, inspect the functions individually before trusting the combined result:
print(clean_status(" PAID "))
print(is_paid_status("paid"))
print(is_paid_status("refunded"))
print(cents_to_dollars(4200))
Expected output:
paid
True
False
42.0
This is a practical debugging habit: verify each small rule independently, then verify the loop that combines them. If the final list is wrong, you have only a few possible locations to inspect rather than one large block of mixed logic.
Before moving on, review each function against this checklist:
- Its name says what it does.
- Its parameters represent only inputs it genuinely needs.
- Its docstring is the first statement in the body.
- It returns a value that the caller can reuse.
- It does one focused job.
- It does not needlessly print output or alter the original transactions.
Key takeaways
A function gives reusable code a clear interface:
def function_name(parameter):
"""Concise statement of the function's behavior."""
return result
The main ideas are:
- Parameters are names in the function definition; arguments are the concrete values supplied in a call.
- A function call with parentheses runs the function. The name without parentheses refers to the function itself.
returnsends a value back to the caller, making the function usable in assignments and later calculations.- A function without an explicit return value returns
None, even if it prints something. - Single-purpose functions are easier to read, combine, debug, and test.
- A concise docstring belongs as the first statement in the function body and should accurately describe the behavior.
Next, you will use official Python documentation to choose, import, and call a suitable standard-library function. That will extend the same habit: read a function’s inputs and outputs, use it deliberately, and verify its behavior rather than treating code as something to copy blindly.
Can't find a good explanation? Sign up and we'll make it for you
Sign up