Functions
Functions are probably the most useful tool for organizing Python code. They let you give a name to a piece of behavior, reuse it in multiple places, and hide implementation details behind a clear interface.
Well-designed functions make your code easier to read, test, and change. Poorly designed ones quickly become a source of bugs and confusion.
When designing functions, the following best practices lead to clearer and more testable code:
- Give each function a single, clear responsibility. Aim for functions that do one thing rather than mixing multiple or unrelated behaviors. If you find yourself adding many ands to a function name, consider splitting the function into separate steps. Smaller, focused functions are more understandable, testable, and reusable.
- Use descriptive names that start with a verb. Choose descriptive function names that start with a verb because functions perform actions. Write names like
write(),remove_user(), orvalidate_user_input(), rather thandata(),user(), oruser_input(). This practice makes your code more readable by providing context about what a function does. - Stick to Python naming conventions. Honor Python’s naming conventions, such as using snake case for function names and a leading underscore (
_) for non-public helper functions. - Favor pure functions when possible. When working on core business logic, prefer pure functions that depend only on their inputs and have no side effects, such as hidden I/O operations or modifications to global state. Pure functions are easier to understand and test because they always produce the same output for a given input. Side effects are often unavoidable at system boundaries—for example, file I/O or network calls—but keeping them isolated improves maintainability.
- Limit the number of parameters. Functions with many parameters are harder to understand and call correctly. As a general guideline, if a function needs more than three to five parameters, consider grouping related values into a data class or another compound object. This approach improves readability and reduces the risk of passing arguments in the wrong order.
- Use keyword-only arguments with sensible defaults. For optional or configuration-style parameters, prefer keyword-only arguments with sensible defaults over long positional argument lists. This approach makes calls self-documenting and reduces the likelihood of mistakes.
- Return consistent types. Design functions so that they always return the same type of object. Avoid functions that sometimes return a useful value and sometimes
None, or that mix unrelated return values. Consistent return types make your code easier to use, test, and type-check. - Avoid hidden work and surprises. Avoid hiding expensive operations, such as network calls or file I/O, in functions with innocuous names like
get_config()unless that behavior is clearly expected. Callers should have a clear understanding of a function’s purpose based on its name, parameters, and docstring.
To see some of these best practices in action, consider a function that filters users, writes a CSV report, and optionally emails it:
🔴 Avoid this:
import csv
def process_users(users, min_age, filename, send_email):
adults = []
for user in users:
if user["age"] >= min_age:
adults.append(user)
with open(filename, mode="w", newline="", encoding="utf-8") as csv_file:
writer = csv.writer(csv_file)
writer.writerow(["name", "age"])
for user in adults:
writer.writerow([user["name"], user["age"]])
if send_email:
# Emailing logic here...
return adults, filename
This function performs several tasks. It filters data, writes it to a file, emails the report, and returns a tuple of mixed and unrelated values. It also relies on multiple positional arguments and a Boolean flag, send_email, that alters the function’s behavior, making calls harder to read and easier to misuse.
✅ Favor this:
import csv
def filter_adult_users(users, *, min_age=18):
"""Return users whose age is at least min_age."""
return [user for user in users if user["age"] >= min_age]
def save_users_csv(users, filename):
"""Save users to a CSV file."""
with open(filename, mode="w", newline="", encoding="utf-8") as csv_file:
writer = csv.writer(csv_file)
writer.writerow(["name", "age"])
for user in users:
writer.writerow([user["name"], user["age"]])
def send_users_report(filename):
"""Send the report."""
# Emailing logic here...
In this updated version, you split the code into three functions with a single responsibility each. One function filters users, one handles file output, and one manages email delivery. Each function has a clear purpose, predictable behavior, and a focused interface.
Taken together, these changes illustrate how smaller, well-named, and focused functions are easier to understand, test, and reuse than a single large function that performs many different tasks.
Related Resources
Tutorial
Defining Your Own Python Function
Learn how to define your own Python function, pass data into it, and return results to write clean, reusable code in your programs.
For additional information on related topics, take a look at the following resources:
- How to Use Python Lambda Functions (Tutorial)
- Python Inner Functions: What Are They Good For? (Tutorial)
- Python Closures: Common Use Cases and Examples (Tutorial)
- Using Python Optional Arguments When Defining Functions (Tutorial)
- What Does -> Mean in Python Function Definitions? (Tutorial)
- Using and Creating Global Variables in Your Python Functions (Tutorial)
- Functional Programming in Python: When and How to Use It (Tutorial)
- Defining Main Functions in Python (Tutorial)
- Your Guide to the Python print() Function (Tutorial)
- Python Timer Functions: Three Ways to Monitor Your Code (Tutorial)
- Python's filter(): Extract Values From Iterables (Tutorial)
- Python's map(): Processing Iterables Without a Loop (Tutorial)
- Python's reduce(): From Functional to Pythonic Style (Tutorial)
- How Do You Choose Python Function Names? (Tutorial)
- Python's Built-in Functions: A Complete Exploration (Tutorial)
- Defining and Calling Python Functions (Course)
- Defining Your Own Python Function (Quiz)
- Defining and Calling Python Functions (Quiz)
- Using Python Lambda Functions (Course)
- Python Lambda Functions (Quiz)
- Python Inner Functions (Course)
- Python Inner Functions: What Are They Good For? (Quiz)
- Exploring Python Closures: Examples and Use Cases (Course)
- Python Closures: Common Use Cases and Examples (Quiz)
- Defining Python Functions With Optional Arguments (Course)
- Using Python Optional Arguments When Defining Functions (Quiz)
- What Does -> Mean in Python Function Definitions? (Quiz)
- Working With Global Variables in Python Functions (Course)
- Using and Creating Global Variables in Your Python Functions (Quiz)
- Using Functional Programming in Python (Course)
- Functional Programming in Python: When and How to Use It (Quiz)
- Defining Main Functions in Python (Course)
- Defining Main Functions in Python (Quiz)
- The Python print() Function: Go Beyond the Basics (Course)
- The Python print() Function (Quiz)
- The Python print() Function (Quiz)
- Filtering Iterables With Python (Course)
- Python's map() Function: Transforming Iterables (Course)
- How Do You Choose Python Function Names? (Quiz)
- Python's Built-in Functions: A Complete Exploration (Quiz)