10xdev Book: Programming from Zero to Pro with Python
Chapter 13: Scalable Project Organization: Modules and Packages
Chapter Introduction
You’ve got your environment set up with uv, you know how to pull in powerful external libraries, and you can handle errors like a pro. That’s awesome! But as your projects get bigger, just having a bunch of code in one file, or even several files dumped in the same folder, starts to feel chaotic. It becomes hard to find things, hard to understand the flow, and really hard for someone else (or your future self!) to jump in.
This chapter is about bringing structure to your own code. We’ll learn the standard Python ways to break down large projects into smaller, logical pieces, making them scalable, maintainable, and much easier to work with. Think of it as organizing your workshop so you know exactly where every tool is. We’ll cover:
- Modules: Splitting your code into separate
.pyfiles based on functionality. - Packages: Grouping related modules together into folders.
This isn’t just about tidiness; it’s about building projects that can grow without collapsing under their own weight.
1. Modules: Your Code Chapters
The simplest way to organize is using modules.
A module is just any Python file (.py) containing Python code (functions, classes, variables).
Instead of putting everything in your main app.py, you can create separate files for related tasks. For example:
utils.py: For helper functions used across your project.database.py: For functions interacting with your database.api_client.py: For code that talks to an external API.
Then, your main script can import the functions or classes it needs from these modules.
Creating and Using a Module
Step 1: Create the Module File
Let’s make a simple module for string utilities. Create string_utils.py:
# File: string_utils.py
def reverse_string(s: str) -> str:
"""Reverses a given string."""
return s[::-1]
def count_vowels(s: str) -> int:
"""Counts vowels (a, e, i, o, u) in a string, case-insensitive."""
vowels = "aeiou"
count = 0
for char in s.lower():
if char in vowels:
count += 1
return count
# Good practice: Add a test block
if __name__ == "__main__":
print("Testing string_utils...")
test_string = "Hello World"
print(f"'{test_string}' reversed: {reverse_string(test_string)}")
print(f"'{test_string}' has {count_vowels(test_string)} vowels.")
Step 2: Import and Use in Another File
Now, create main.py in the same folder:
# File: main.py
import string_utils # Import the whole module
my_text = "Python Programming"
reversed_text = string_utils.reverse_string(my_text)
vowel_count = string_utils.count_vowels(my_text)
print(f"Original: '{my_text}'")
print(f"Reversed: '{reversed_text}'")
print(f"Vowel count: {vowel_count}")
# Note: The test code from string_utils.py does NOT run here!
The if __name__ == "__main__" Guard (Crucial!)
Notice the if __name__ == "__main__": block in string_utils.py. This is essential:
- Code inside this block only runs when you execute
python string_utils.pydirectly. - It does not run when the file is
imported by another file (likemain.py).
This lets you include tests, examples, or setup code within your module file without it interfering when the module is used elsewhere. Always use this guard for any executable code in your modules!
2. Packages: Organizing Your Modules into Folders
When you have many modules, just dumping them all in one folder gets messy again. A package lets you group related modules into a sub-directory.
A package is simply a folder that contains:
- One or more Python module files (
.py). - A special (usually empty) file named
__init__.py.
The __init__.py file tells Python: “This folder isn’t just a folder; it’s a package you can import from.”
Example Package Structure
Let’s organize a hypothetical web app:
my_web_app/
│
├── main.py # Main application entry point
│
└── app/ # The main application package <--- FOLDER
├── __init__.py # Makes 'app' a package (can be empty)
├── core/ # Package for core logic <--- FOLDER
│ ├── __init__.py
│ └── config.py # Configuration module
├── models/ # Package for data models <--- FOLDER
│ ├── __init__.py
│ └── user.py # User model module
└── services/ # Package for business logic <--- FOLDER
├── __init__.py
└── user_service.py # Logic for user operations
Importing from Packages
From main.py (which is outside the app package), you use dot notation to access modules inside the packages:
# In main.py
from app.core.config import AppSettings # Import class from module in sub-package
from app.services.user_service import get_user_by_id # Import function
settings = AppSettings()
user = get_user_by_id(user_id=123)
print(f"Running app: {settings.APP_NAME}")
if user:
print(f"Found user: {user.name}")
Modules inside the same package can also import each other using relative imports (starting with .) or absolute imports (starting from the top-level package name).
# Inside app/services/user_service.py
# Absolute import from another module within the 'app' package
from app.models.user import User
# Or a relative import (means "from the 'models' sibling package, import user")
# from ..models import user # Less common, sometimes harder to follow
def get_user_by_id(user_id: int) -> User | None:
# ... logic to fetch user ...
# Return an instance of the User class (defined in models/user.py)
if user_id == 123:
return User(id=123, name="Alice")
return None
# Need this dummy class for the example to run without errors
class User:
def __init__(self, id, name):
self.id = id
self.name = name
# Need this dummy class for the example to run without errors
class AppSettings:
APP_NAME = "My Web App"
This structured approach, separating concerns like configuration (core), data definitions (models), and business logic (services), is key to building scalable and maintainable applications.
3. Applied Project: Organizing the Unit Converter
Let’s take the unit converter example and structure it using a package.
Desired Structure:
converter_app/
│
├── main.py
│
└── converters/ # Package
├── __init__.py
├── distance.py # Module for km <-> miles
└── temperature.py # Module for C <-> F
Step 1: Create Files and Folders
Create the structure above. Make __init__.py empty.
Step 2: Add Code to Modules
File: converters/temperature.py
# converters/temperature.py
def celsius_to_fahrenheit(celsius: float) -> float:
"""Converts Celsius to Fahrenheit."""
return (celsius * 9/5) + 32
def fahrenheit_to_celsius(fahrenheit: float) -> float:
"""Converts Fahrenheit to Celsius."""
return (fahrenheit - 32) * 5/9
if __name__ == "__main__":
print(f"100 C = {celsius_to_fahrenheit(100)} F")
print(f"212 F = {fahrenheit_to_celsius(212)} C")
File: converters/distance.py
# converters/distance.py
KM_TO_MILES = 0.621371
def km_to_miles(kilometers: float) -> float:
"""Converts kilometers to miles."""
return kilometers * KM_TO_MILES
def miles_to_km(miles: float) -> float:
"""Converts miles to kilometers."""
return miles / KM_TO_MILES
if __name__ == "__main__":
print(f"10 km = {km_to_miles(10):.2f} miles")
print(f"10 miles = {miles_to_km(10):.2f} km")
Step 3: Write the Main Script
File: main.py
# main.py - Located outside the 'converters' package
# Import specific functions from modules within the package
from converters.temperature import celsius_to_fahrenheit
from converters.distance import km_to_miles
print("--- Unit Converter ---")
temp_c = 25.0
temp_f = celsius_to_fahrenheit(temp_c)
print(f"{temp_c}°C is {temp_f:.1f}°F")
dist_km = 100.0
dist_mi = km_to_miles(dist_km)
print(f"{dist_km} km is {dist_mi:.2f} miles")
Run it: Navigate your terminal to the converter_app folder (the one containing main.py and the converters folder) and run python main.py.
This structure is clean! main.py is simple, and the conversion logic is neatly tucked away in the converters package. Need to add currency conversion? Just add currency.py inside converters!
4. Chapter Summary
- Modules (
.pyfiles) break code into logical units. - Packages (folders with
__init__.py) group related modules. - Use
import package.moduleorfrom package.module import itemto access code across files. - The
if __name__ == "__main__":guard is essential for creating reusable modules with self-contained tests or examples. - Organizing projects into modules and packages is critical for scalability, maintainability, and team collaboration.
You’ve now learned all the core concepts and professional practices for writing well-structured Python 3.14 code and managing dependencies. It’s time to put it all together! In the final chapter, we’ll build a complete To-Do List application from scratch, applying everything you’ve learned. Let’s build something real!