SECTION 1
Historical Context & Motivation
In the earliest days of computing, programs were small enough to fit on a single page. Programmers would write every instruction in one continuous file, and things worked fine—for a while. As software grew more complex in the 1960s and 1970s, developers realized that cramming thousands of lines into one file made code almost impossible to read, debug, or share with teammates. The concept of modularity—breaking a program into separate, self-contained pieces—became one of the most important ideas in all of computer science.
Python, created by Guido van Rossum in 1991, was designed from the start to support modular programming. Its import system lets you pull functions, variables, and classes from one file into another with a single line of code. This system draws on decades of programming language evolution, from early Fortran subroutine libraries to the module systems of languages like Modula-2 and Ada.
1957 • Fortran & Subroutine Libraries
1978 • Modula-2 & Formal Modules
1991 • Python 0.9 Released
2000s • Package Managers & PyPI
Today, virtually every real-world Python application—from web servers to data science notebooks—is organized across multiple files. The question this lesson addresses is straightforward: How do you split your code into separate files and then bring the pieces back together using Python's import statement?
SECTION 2
Core Principles of Multi-File Organization
Before you start splitting code into files, you need to understand a few foundational ideas. These principles explain why multi-file programs work, why they are useful, and how Python's import mechanism ties everything together.
Module
.py) that contains functions, variables, or classes. Any .py file you create is already a module.Import Statement
Namespace
calculate() without conflict.Separation of Concerns
Reusability
KEY TAKEAWAY
SECTION 3
Visual Explanation — How Imports Connect Files
The diagram below shows a small multi-file Python project. The main program file, main.py, imports functions from two helper modules: math_helpers.py and greeting.py. Notice how import arrows flow from the main file toward the modules it depends on. Each module contains its own functions, and main.py can use any function it imports.
main.py imports specific functions from two helper modules. The dashed arrows show the direction of the import—main.py reaches into each module to grab only the functions it needs.In the diagram, notice that math_helpers.py also contains a subtract() function, but main.py only imports add. This is one advantage of the from ... import ... syntax—you bring in only what you need, keeping your namespace clean and your code easy to follow.
SECTION 4
How Python's Import System Works
When Python encounters an import statement, it follows a specific process behind the scenes. Understanding this process helps you avoid common errors and structure your projects correctly.
The Three Steps of Importing
- Step 1 — Locate the file. Python searches for the module in a set of directories, starting with the folder where your main script lives. If the module file is in the same folder, Python finds it immediately.
- Step 2 — Execute the file. Python runs the entire module file from top to bottom. All function definitions, variable assignments, and class definitions in that file become available.
- Step 3 — Bind names. Depending on which import syntax you used, Python either creates a reference to the whole module or places specific names directly into your current file's namespace.
Import Syntax Options
Python gives you several ways to import. The two most common for beginners are shown below. Each has a slightly different effect on how you call the imported function.
| Syntax | How You Call the Function | What Happens |
|---|---|---|
import math_helpers | math_helpers.add(3, 5) | The entire module is loaded. You access functions using dot notation (module_name.function_name). |
from math_helpers import add | add(3, 5) | Only the specified function is brought into your namespace. You call it directly by name. |
from math_helpers import add, subtract | add(3, 5) or subtract(10, 4) | Multiple specific functions are imported at once. Separate names with commas. |
from math_helpers import * | add(3, 5) | All names from the module are imported. This is generally discouraged because it can cause name collisions. |
Common import syntax options in Python
Important Rule
main.py and math_helpers.py are in different folders, Python won't find the module without additional configuration. For now, always keep your project files together.SECTION 5
Detailed Breakdown — Structuring Your Project
Knowing the syntax is only half the battle. You also need to decide which functions belong in which file. A well-organized project groups related functions together and keeps the main program file focused on running the show. The diagram below shows a slightly larger project that a high school student might build—a simple quiz game.
quiz.py imports from three helper modules, each responsible for a different concern: generating questions, scoring answers, and displaying output.When deciding how to split your code, ask yourself: Does this group of functions share a single purpose? If yes, they belong in the same module. If a function does something clearly different—like displaying output versus calculating a score—it should go in a separate file. You don't need dozens of files for a small project; even splitting into just two files (a helper module and a main script) is a huge improvement over one massive file.
Naming Tip
math_helpers.py, string_utils.py. Bad names: Math Helpers.py, 2helpers.py.SECTION 6
Worked Example — Building a Tip Calculator with Two Files
Let's walk through a complete example. You are building a simple tip calculator. You'll create a helper module called tip_helpers.py that contains the calculation function, and a main script called main.py that handles user interaction.
Tip Calculator — Two-File Setup
- 1
Step 1 — Create the helper module (tip_helpers.py)
Create a new file calledtip_helpers.py. Define a function calledcalculate_tipthat takes two parameters: the bill amount and the tip percentage. The function returns the tip amount.def calculate_tip(bill, tip_percent): return bill * (tip_percent / 100) - 2
Step 2 — Add a second helper function
In the same file, add a function calledcalculate_totalthat returns the bill plus the tip. This keeps all math logic in one module.def calculate_total(bill, tip_percent): tip = calculate_tip(bill, tip_percent) return bill + tip - 3
Step 3 — Create the main script (main.py)
Create a second file calledmain.pyin the same folder. At the top, import the two functions you need fromtip_helpers. Notice that you write the module name without the.pyextension.from tip_helpers import calculate_tip, calculate_total - 4
Step 4 — Write the user interaction code
Below the import, add code that asks the user for input, calls the imported functions, and prints the results.bill = float(input("Enter bill amount: $")) percent = float(input("Tip percentage: ")) tip = calculate_tip(bill, percent) total = calculate_total(bill, percent) print(f"Tip: ${tip:.2f}") print(f"Total: ${total:.2f}") - 5
Step 5 — Run the program
Open a terminal in the project folder and runpython main.py. Python findstip_helpers.pyin the same directory, loads the functions, and your program works. If the user enters a bill of $50 and a 20% tip, the output is:Tip: $10.00 Total: $60.00
WHY THIS MATTERS
main.py is short and readable—it only handles input and output. All the math lives in tip_helpers.py. If you later build a restaurant receipt printer, you can import the same calculate_tip function into that new project without rewriting anything.SECTION 7
Single-File vs. Multi-File — Strengths & Limitations
Not every program needs to be split across multiple files. For tiny scripts—say, 20 lines or fewer—a single file is perfectly fine. But as your projects grow, the advantages of multi-file organization become overwhelming. The table below compares the two approaches across several important dimensions.
| Dimension | Single-File Approach | Multi-File Approach |
|---|---|---|
| Readability | Gets harder to navigate as lines increase. Scrolling through hundreds of lines to find a function is frustrating. | Each file is short and focused. You can quickly locate any function by opening the right module. |
| Reusability | Functions are trapped inside one file. To reuse them, you'd have to copy-paste. | Any module can be imported into future projects with a single line. |
| Collaboration | Multiple people editing the same file often leads to merge conflicts. | Team members can each work on different modules simultaneously. |
| Debugging | An error anywhere could be anywhere—you search the entire file. | Error messages point to the specific module and line number, narrowing your search. |
| Setup Complexity | Zero setup—just write code. Great for quick experiments. | Requires planning: deciding which functions go where, naming modules, keeping files in one folder. |
Single-file vs. multi-file Python programs
WHEN TO SPLIT
SECTION 8
Connection to Advanced Concepts
The skills you're building now lay the groundwork for much more powerful Python features. As you advance, you'll encounter packages, third-party libraries, and object-oriented modules. Understanding how a basic import works makes all of these topics feel like natural extensions rather than entirely new ideas.
| What You Learn Now | What It Leads To |
|---|---|
| Importing functions from a file in the same folder | Packages — folders of modules with an __init__.py file that Python treats as a single importable unit |
Writing helper modules like math_helpers.py | Third-party libraries — modules published by other developers (e.g., import requests) that you install via pip |
Using from module import func syntax | Selective imports & aliases — techniques like import numpy as np used throughout data science |
| Separating concerns across files | Software architecture — patterns like MVC (Model-View-Controller) used in professional web and app development |
From beginner imports to professional Python practices
You may also encounter the if __name__ == "__main__": guard. This special block lets a module behave differently when it is run directly versus when it is imported by another file. Code placed inside that block only runs if the file is the entry point, not when it is imported. This is an important pattern you'll explore as you build larger programs.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
import utils and from utils import greet. How does each affect the way you call the greet function?Show answer
import utils, Python loads the entire module and you must use dot notation to call the function: utils.greet(). When you write from utils import greet, Python pulls only the greet function into your current namespace, so you call it directly as greet(). The first approach keeps names clearly associated with their module, reducing the risk of name collisions. The second approach is more concise when you only need one or two items from a module.Question 2: BASIC CALCULATION
conversions.py containing this function:
def cm_to_inches(cm):
return cm * 0.3937
Write the import statement you would place in main.py (same folder) and show how you would call the function to convert 25 cm to inches and print the result.Show answer
main.py, write: from conversions import cm_to_inches. Then call it: result = cm_to_inches(25) followed by print(result). This would output approximately 9.8425. Alternatively, you could use import conversions and then call conversions.cm_to_inches(25).Question 3: INTERMEDIATE
geometry.py contains:
def area_rect(length, width):
return length * width
def perimeter_rect(length, width):
return 2 * (length + width)
In main.py, you write from geometry import area_rect. Can you now call perimeter_rect(5, 3) in main.py? Explain why or why not, and describe how to fix it if needed.Show answer
perimeter_rect(5, 3) because only area_rect was imported. Python will raise a NameError saying that perimeter_rect is not defined. To fix this, change the import line to from geometry import area_rect, perimeter_rect. Alternatively, use import geometry and then call geometry.perimeter_rect(5, 3). Both solutions give you access to the perimeter function.Question 4: APPLIED
main.py would need.Show answer
grades.py containing calculate_average(grades_list) and letter_grade(average); (2) student_data.py containing add_student(name, grades) and get_student(name); (3) display.py containing print_report(student). In main.py, you'd write: from grades import calculate_average, letter_grade, from student_data import add_student, get_student, and from display import print_report. Each teammate works on one module without conflicting with the others.Question 5: CRITICAL THINKING
file_a.py and file_b.py. If file_a.py imports from file_b.py, and file_b.py also imports from file_a.py, what potential problem could arise? Suggest at least one strategy to avoid it.Show answer
file_a, it starts executing it and encounters an import of file_b. Python then starts executing file_b, which tries to import file_a—but file_a hasn't finished loading yet. This can lead to ImportError or partially loaded modules with missing names. To avoid this, restructure your code so imports flow in one direction: move the shared functions into a third module (e.g., shared.py) that both files can import from without importing each other.SUMMARY
Lesson Summary
Summary
In this lesson, you learned that a module is any Python file ending in .py, and you can bring its contents into another file using an import statement. The two primary syntaxes are import module_name (which requires dot notation) and from module_name import function_name (which lets you call the function directly). For simple projects, all files must be in the same folder.
Splitting your program across multiple files follows the principle of separation of concerns: each module handles one specific job. This makes your code more readable, more reusable, and easier to debug. As you advance, these same skills will prepare you for working with packages, third-party libraries, and professional software architecture patterns.