Python 30‑by‑30 Course
It's time to get organized! In this module, you'll learn how to bundle your code into reusable tools called functions, import code from other files, and handle more complex, nested data. This is how we go from writing simple scripts to building structured programs.
In Module 2, we learned how to control the flow of our programs:
and
/or
and learned about nesting if
statements.for
loops for iterating over sequences (like a list or range()
) and while
loops for repeating code as long as a condition is true.try...except
blocks to catch potential errors like ValueError
and ZeroDivisionError
.Have you ever found yourself writing the same few lines of code over and over? That's a sign you need a function! A function is a named block of reusable code that performs a specific task. You define it once using the def
keyword, and then you can "call" it by name as many times as you want. This is a core principle of programming: Don't Repeat Yourself (DRY).
Functions can take inputs, called parameters (or arguments), which are variables they use to do their work. For example, a greet(name)
function would take a name
as a parameter. They can also give back a result using the return
statement. Good functions are like specialized tools: they do one thing and do it well. For example, you might write a function to calculate the area of a circle, or another to validate a user's email address.
It's also great practice to write a short explanation of what your function does right below its definition. This is called a docstring, and you create it with triple quotes """..."""
. It helps other people (and your future self!) understand your code without having to read every line.
Write a function named celsius_to_fahrenheit
that takes one number (a temperature in Celsius) as a parameter and returns the equivalent temperature in Fahrenheit. The formula is $(C \times 9/5) + 32$. Test it with a few values like 0, 20, and 100.
# temperature_converter.py
def celsius_to_fahrenheit(celsius_temp):
"""Converts a temperature from Celsius to Fahrenheit."""
fahrenheit = (celsius_temp * 9/5) + 32
return fahrenheit
# --- Let's test it ---
temp1 = 0
temp2 = 20
print(f"{temp1}°C is {celsius_to_fahrenheit(temp1)}°F")
print(f"{temp2}°C is {celsius_to_fahrenheit(temp2)}°F")
As you write more functions, your script file can get long and messy. The solution is to organize your code into separate files called modules. A module is simply a Python file (with a .py
extension) containing functions, classes, or variables that you can import
into other scripts. This keeps your code clean and reusable across different projects.
Python comes with a huge standard library of pre-made modules that you can use right away. Need to work with dates? import datetime
. Need random numbers? import random
. These are powerful tools you don't have to build from scratch. You can create your own modules just as easily. If you save a file named my_tools.py
, you can use import my_tools
in another file to access all its functions.
But the real power comes from the global Python community. There's a giant online repository of code called the Python Package Index (PyPI). You can use a command-line tool called pip
to install these "third-party packages." For example, running pip install requests
gives you a powerful module for interacting with websites, saving you hundreds of hours of work.
Create a module named string_utils.py
. Inside it, define a function reverse_string(s)
that returns a reversed version of a string. In a separate main script, import your new module and use it to reverse a string that the user provides.
# string_utils.py (this is one file)
def reverse_string(s):
"""Returns the reversed version of a string s."""
return s[::-1] # A cool Python slicing trick!
# ------------------------------------------------
# main_script.py (this is a separate file in the same folder)
import string_utils
user_text = input("Enter some text to reverse: ")
reversed_text = string_utils.reverse_string(user_text)
print(f"Here it is reversed: {reversed_text}")
Hundreds of hours of research and development have gone into creating this free course. If you're enjoying the lessons, please consider a small token of appreciation as thanks.
Your support helps cover ongoing server costs and, more importantly, fuels the creation of more free, high-quality learning materials for everyone. Thank you!
Buy Me a Coffee ☕So far, our lists and dictionaries have been simple. But in the real world, data is often nested. You might have a list of lists to represent a tic-tac-toe board, or a dictionary where a value is another dictionary, like a user profile with a nested dictionary for 'address'. This is incredibly common when you get data from websites (in a format called JSON).
Working with this kind of data can be tricky. How do you find something buried deep inside? One powerful, though sometimes mind-bending, technique is recursion. A recursive function is a function that calls itself to solve a smaller piece of the same problem. Think of it as breaking a big problem down into identical, smaller sub-problems until you reach a simple one you can solve directly.
Every recursive function needs two things: 1) a base case—a condition that tells the function when to stop calling itself—and 2) a recursive step, where it calls itself with a slightly simpler version of the problem. It's perfect for navigating hierarchical data, like a file system where you need to process every file in every sub-folder.
Write a recursive function countdown(n)
that prints numbers from `n` down to 1. The base case is when `n` is 0, at which point the function should just stop. The recursive step should print the current `n` and then call countdown(n-1)
.
# recursive_countdown.py
def countdown(n):
# Base Case: If n is 0 or less, we stop.
if n <= 0:
print("Blast off! 🚀")
return # This ends the function call
# Recursive Step: Print the number, then call itself with a smaller number.
print(n)
countdown(n - 1)
# --- Let's try it ---
countdown(5)
map
and filter
for transforming data.lambda
functions.Python is famous for its clean and readable syntax. One of the best examples of this is comprehensions. A comprehension is a compact way to create a new list, set, or dictionary from an existing sequence. It lets you replace a multi-line `for` loop with a single, elegant line of code. For example, instead of creating an empty list and looping to add the square of each number, you can just write squares = [n*n for n in numbers]
.
Python also has tools for a style of programming called "functional programming." Two key functions here are map
and filter
. `map` applies a function to every single item in a list (e.g., convert a list of strings to all lowercase). `filter` creates a new list containing only the items from an original list that pass a certain test (e.g., keep only the numbers greater than 10).
Sometimes, the function you want to use with `map`, `filter`, or sorting is so simple that it feels silly to define it with `def`. For these one-off uses, Python gives you lambda functions. They are small, anonymous (unnamed) functions that you can define right where you need them. They are perfect for short, simple operations.
You have a list of numbers: nums = [1, 2, 3, 4, 5, 6]
. Use a list comprehension to create a new list containing only the even numbers from the original list. Then, use another list comprehension to create a list of the squares of those even numbers.
# comprehension_practice.py
nums = [1, 2, 3, 4, 5, 6]
# 1. Use a list comprehension with a condition to get even numbers
even_numbers = [n for n in nums if n % 2 == 0]
print(f"Even numbers: {even_numbers}") # Expected: [2, 4, 6]
# 2. Use a list comprehension to square the even numbers
squared_evens = [n*n for n in even_numbers]
print(f"Squared evens: {squared_evens}") # Expected: [4, 16, 36]
Imagine working on two different projects. Project A needs an old version of a library, but Project B needs the newest version. If you install them both on your main computer, they'll conflict! This is a common headache, and the solution is virtual environments.
A virtual environment is an isolated, self-contained directory for your project. It has its own Python interpreter and its own set of installed packages. This means you can create a separate, clean workshop for every project you work on, and the tools (packages) from one won't interfere with another. It's a best practice that will save you countless problems down the line. You can create one easily with the command: python3 -m venv venv
.
Once your environment is "activated," you can use `pip` to install packages just for that project. To make your project easy for others to use, you can create a list of all the packages it needs. The standard way to do this is with a requirements.txt
file. You can generate it with one command (pip freeze > requirements.txt
), and anyone else can then install all the necessary packages with another (pip install -r requirements.txt
).
Time to put it all together. Create a new project folder. Inside it, create a new virtual environment and activate it. Once activated, use `pip` to install the `requests` library. Check that it installed correctly. Finally, create the `requirements.txt` file for your project.
# On your command line / terminal:
# 1. Create a new folder and move into it
mkdir my-new-project
cd my-new-project
# 2. Create the virtual environment (this creates a 'venv' folder)
python3 -m venv venv
# 3. Activate the environment
# On macOS/Linux:
source venv/bin/activate
# On Windows:
venv\Scripts\activate
# (Your prompt should now change to show '(venv)')
# 4. Install a package
pip install requests
# 5. Create the requirements file
pip freeze > requirements.txt
# 6. When you're done, deactivate the environment
deactivate