22. Organizing Code with Modules and Packages
As your Python programs grow larger, keeping all your code in a single file becomes impractical. You'll want to organize related functions, classes, and variables into separate files that can be reused across different programs. Python's module and package system provides exactly this capability—a way to organize, share, and reuse code effectively.
In this chapter, we'll explore how Python's import system works, how to create and use your own modules, and how to organize multiple modules into packages. We'll also examine the special __name__ variable that lets you write files that work both as importable modules and as standalone scripts.
22.1) What Modules Are and How import Works
Understanding Modules
A module is simply a Python file containing definitions and statements. Any .py file you create is a module. When you write a function in a file called calculator.py, that file becomes a module named calculator that other Python files can use.
Modules serve several important purposes:
- Code Reusability: Write a function once and use it in multiple programs
- Organization: Group related functionality together
- Namespace Management: Keep names separate to avoid conflicts
- Maintainability: Smaller, focused files are easier to understand and modify
Let's create a simple module to see how this works. Create a file named greetings.py:
# greetings.py
def say_hello(name):
"""Return a friendly greeting."""
return f"Hello, {name}!"
def say_goodbye(name):
"""Return a farewell message."""
return f"Goodbye, {name}. See you soon!"
# A module-level variable
default_greeting = "Welcome"This file is now a module. It contains two functions and one variable that other Python files can use.
The import Statement
To use code from a module, you import it. The import statement tells Python to load a module and make its contents available. Create another file in the same directory called main.py:
# main.py
import greetings
message = greetings.say_hello("Alice")
print(message) # Output: Hello, Alice!
farewell = greetings.say_goodbye("Bob")
print(farewell) # Output: Goodbye, Bob. See you soon!
print(greetings.default_greeting) # Output: WelcomeWhen you run main.py, Python executes the import greetings statement. Here's what happens behind the scenes:
Important: Python executes a module's code only the first time it's imported in a program. Subsequent imports in the same program reuse the already-loaded module. This prevents duplicate execution and saves time.
Accessing Module Contents
After importing a module, you access its contents using dot notation: module_name.item_name. This is similar to how we access string methods like text.upper() or list methods like numbers.append(), as we learned in Chapters 5 and 14.
import greetings
# Access functions
result = greetings.say_hello("Charlie")
# Access variables
greeting = greetings.default_greeting
# You can even check what's in a module
print(dir(greetings)) # Lists all names defined in the moduleThe dot notation makes it clear where each name comes from. When you see greetings.say_hello(), you immediately know this function comes from the greetings module.
Module Search Path
When you write import greetings, how does Python find greetings.py? Python searches for modules in a specific order:
- Current Directory: The directory containing the script you're running
- PYTHONPATH: Directories listed in the
PYTHONPATHenvironment variable (if set) - Standard Library: Python's built-in module directories
- Site-Packages: Third-party packages installed with pip
You can see Python's search path by examining sys.path:
import sys
for path in sys.path:
print(path)Output (example - your actual paths will differ based on your system and Python installation):
/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packagesThe first path in the output is the current working directory. Python searches this directory first, so it can find modules in the same directory.
Module Names and File Names
The module name is the filename without the .py extension. If your file is string_utils.py, the module name is string_utils. Module names must follow Python's identifier rules (as we learned in Chapter 3):
- Start with a letter or underscore
- Contain only letters, digits, and underscores
- Cannot be Python keywords
# Valid module names (and filenames)
import data_processor # data_processor.py
import user_auth # user_auth.py
import _internal_helpers # _internal_helpers.py
# Invalid - would cause errors
# import 2d_graphics # Can't start with digit
# import my-module # Hyphens not allowed
# import class # 'class' is a keywordCommon Pitfall: Shadowing Standard Library Modules
Be careful not to name your modules the same as standard library modules. If you create a file called random.py in your project directory, Python will import your file instead of the standard library's random module, causing confusing errors:
# Your file: random.py
def my_function():
return 42
# Another file in your project
import random
print(random.randint(1, 6)) # ERROR! Your random.py doesn't have randint()To avoid this, check if a name is already used by the standard library before creating a module with that name. You can check by trying to import it in the Python interactive shell. If it imports without error, that name is already taken.
What Happens During Import
Let's examine what actually happens when you import a module. Create a file called demo_module.py:
# demo_module.py
print("Module is being loaded!")
def greet():
print("Hello from demo_module")
print("Module loading complete!")Now import it:
# test_import.py
print("Before import")
import demo_module
print("After import")
demo_module.greet()Output:
Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_moduleNotice that the print() statements in demo_module.py execute during import. This demonstrates that importing a module runs all its top-level code. Function definitions are stored for later use, but any code outside functions executes immediately.
If you import the same module again in the same program, the loading messages won't appear again:
import demo_module # First import - executes module code
import demo_module # Second import - uses cached module
import demo_module # Third import - still uses cached moduleOutput:
Module is being loaded!
Module loading complete!The module code runs only once, no matter how many times you import it.
22.2) Different Ways to Import: import, from, and as
Python provides several ways to import modules and their contents. Each approach has different implications for how you access imported names and how they affect your namespace.
Basic import Statement
The basic import statement we've already seen loads the entire module:
import math
result = math.sqrt(16)
print(result) # Output: 4.0
pi_value = math.pi
print(pi_value) # Output: 3.141592653589793With this approach, you always use the module name as a prefix. This makes code very clear—you can always tell where a name comes from.
Importing Specific Names with from
Sometimes you only need one or two items from a module. The from statement lets you import specific names directly into your namespace:
from math import sqrt, pi
result = sqrt(25) # No 'math.' prefix needed
print(result) # Output: 5.0
print(pi) # Output: 3.141592653589793Now you can use sqrt and pi directly without the math. prefix. This is convenient when you use these names frequently.
Let's see another example with our greetings module:
# Using from import
from greetings import say_hello
message = say_hello("Diana") # Direct access
print(message) # Output: Hello, Diana!
# However, say_goodbye is not available since we didn't import it
# say_goodbye("Diana") # NameError: name 'say_goodbye' is not definedYou can import multiple names in one statement:
from greetings import say_hello, say_goodbye, default_greeting
print(say_hello("Eve")) # Output: Hello, Eve!
print(say_goodbye("Frank")) # Output: Goodbye, Frank!
print(default_greeting) # Output: WelcomeThe Wildcard Import (and Why to Avoid It)
Python allows importing everything from a module using *:
from math import *
print(sqrt(9)) # Output: 3.0
print(cos(0)) # Output: 1.0
print(pi) # Output: 3.141592653589793This imports all public names from the module (names not starting with underscore). While this seems convenient, it's generally considered bad practice because:
- Namespace Pollution: You don't know exactly what names you're importing
- Name Conflicts: Imported names might overwrite your own variables
- Readability: Code readers can't tell where names come from
# Problematic example
from math import *
# Later in your code...
def sqrt(x):
"""Your own square root function."""
return x ** 0.5
# Which sqrt are you using? Yours or math's?
result = sqrt(16) # Confusing!Best Practice: Import specific names or use the basic import statement. Avoid from module import * except in interactive sessions where you're experimenting.
Renaming Imports with as
Sometimes module or function names are long, or you want to avoid name conflicts. The as keyword lets you create an alias:
import math as m
result = m.sqrt(36)
print(result) # Output: 6.0This is particularly useful for modules with long names or when following common conventions:
import datetime as dt
today = dt.date.today()
print(today) # Output: 2025-12-19 (or current date)You can also rename specific imports:
from math import sqrt as square_root
result = square_root(49)
print(result) # Output: 7.0This is helpful when you have name conflicts:
from math import sqrt as math_sqrt
def sqrt(x):
"""Custom square root with input validation."""
if x < 0:
return None
return math_sqrt(x)
print(sqrt(25)) # Output: 5.0 (your function)
print(sqrt(-4)) # Output: None (your function)Combining Import Styles
You can mix different import styles in the same file:
import math
from datetime import date, time
from random import randint as random_int
# Use math with prefix
radius = 5
area = math.pi * radius ** 2
# Use date and time directly
today = date.today()
current_time = time(14, 30)
# Use renamed function
dice_roll = random_int(1, 6)Choosing the Right Import Style
Here's a decision guide:
Use import module when:
- You need multiple items from the module
- You want maximum clarity about where names come from
- The module name is short and clear
Use from module import name when:
- You need only one or two specific items
- The names are distinctive and unlikely to conflict
- You'll use the names frequently
Use import module as alias when:
- The module name is very long
- Following a common convention (like
import numpy as np) - You need to avoid conflicts with other modules
Avoid from module import * in production code:
- Use it only for quick experiments in the interactive shell
- Never use it in modules that others will import
Let's see a complete example demonstrating good import practices:
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
def calculate_statistics(numbers):
"""Calculate various statistics for a list of numbers."""
if not numbers:
return None
avg = mean(numbers)
mid = median(numbers)
std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
return {
'mean': avg,
'median': mid,
'std_dev': std_dev,
'timestamp': dt.now()
}
# Test the function
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}") # Output: Mean: 30.0
print(f"Median: {stats['median']}") # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}") # Output: Std Dev: 14.14This example shows:
import mathfor the full module (we might use other math functions later)from statistics import mean, medianfor specific functions we use frequentlyfrom datetime import datetime as dtfor a commonly aliased module
22.3) Overview of Common Python Standard Library Modules
Python comes with a rich standard library—a collection of modules that provide solutions to common programming tasks. These modules are always available; you don't need to install anything extra. Understanding what's available in the standard library helps you avoid "reinventing the wheel."
The math Module
The math module provides mathematical functions beyond basic arithmetic:
import math
# Trigonometric functions
angle_rad = math.radians(45) # Convert degrees to radians
print(math.sin(angle_rad)) # Output: 0.7071067811865476
print(math.cos(angle_rad)) # Output: 0.7071067811865475
# Rounding and absolute value
print(math.ceil(4.2)) # Output: 5 (round up)
print(math.floor(4.8)) # Output: 4 (round down)
print(math.fabs(-7.5)) # Output: 7.5 (absolute value as float)
# Exponential and logarithmic
print(math.exp(2)) # Output: 7.38905609893065 (e^2)
print(math.log(100)) # Output: 4.605170185988092 (natural log)
print(math.log10(100)) # Output: 2.0 (base-10 log)
# Constants
print(math.pi) # Output: 3.141592653589793
print(math.e) # Output: 2.718281828459045As we learned in Chapter 4, the math module is essential for advanced mathematical operations.
The random Module
The random module generates pseudo-random numbers and makes random selections:
import random
# Random integers
dice = random.randint(1, 6) # Random integer from 1 to 6 (inclusive)
print(f"Dice roll: {dice}")
# Random floats
probability = random.random() # Random float from 0.0 to 1.0
print(f"Probability: {probability:.4f}")
# Random choice from a sequence
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
# Shuffle a list in place
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
# Random sample without replacement
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")Output (example - will vary due to randomness):
Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]The datetime Module
The datetime module handles dates and times:
from datetime import date, time, datetime, timedelta
# Current date and time
today = date.today()
now = datetime.now()
print(f"Today: {today}") # Output: Today: 2025-12-19
print(f"Now: {now}") # Output: Now: 2025-12-19 14:30:45.123456
# Creating specific dates and times
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
print(f"Birthday: {birthday}") # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}") # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}") # Output: Appointment: 2025-12-25 10:00:00
# Date arithmetic with timedelta
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}") # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}") # Output: Next week: 2025-12-26
# Extracting components
print(f"Year: {today.year}") # Output: Year: 2025
print(f"Month: {today.month}") # Output: Month: 12
print(f"Day: {today.day}") # Output: Day: 19The os Module
The os module provides operating system functionality. We'll explore this in detail in Chapter 26, but here's a preview:
import os
# Current working directory
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
# List files in a directory
files = os.listdir('.')
print(f"Files: {files[:3]}") # Show first 3 files
# Check if a path exists
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
# Join path components (works across operating systems)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}") # Output: data/users/profile.txt (or data\users\profile.txt on Windows)The sys Module
The sys module provides system-specific parameters and functions:
import sys
# Python version information
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
# Platform information
print(f"Platform: {sys.platform}") # Output: linux, darwin, win32, etc.
# Maximum integer size
print(f"Max int: {sys.maxsize}")The statistics Module
The statistics module provides functions for statistical calculations:
import statistics
grades = [85, 92, 78, 90, 88, 95, 82]
# Central tendency
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
print(f"Mean: {avg}") # Output: Mean: 87.14285714285714
print(f"Median: {mid}") # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
# Spread
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
print(f"Standard deviation: {std_dev:.2f}") # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}") # Output: Variance: 34.81The collections Module
The collections module provides specialized container types. We'll explore this more in Chapter 39, but here's a taste:
from collections import Counter, defaultdict
# Counter - count occurrences
text = "hello world"
letter_counts = Counter(text)
print(letter_counts) # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l']) # Output: 3
# defaultdict - dictionary with default values
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists)) # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}Finding More Standard Library Modules
Python's standard library contains over 200 modules. You can explore them in several ways:
# See all available modules (this takes a moment)
help('modules')
# Get help on a specific module
import math
help(math)
# See what's in a module
import random
print(dir(random))The Python documentation (https://docs.python.org/3/library/) provides comprehensive information about every standard library module. As you gain experience, you'll discover which modules are most useful for your work.
22.4) Creating and Using Your Own Modules
Creating your own modules is straightforward—any Python file can be a module. The key is organizing your code thoughtfully so modules are focused, reusable, and easy to understand.
Creating a Simple Module
Let's create a module for working with student grades. Create a file called grade_calculator.py:
# grade_calculator.py
"""Module for calculating and analyzing student grades."""
def calculate_average(grades):
"""Calculate the average of a list of grades."""
if not grades:
return 0
return sum(grades) / len(grades)
def get_letter_grade(numeric_grade):
"""Convert a numeric grade to a letter grade."""
if numeric_grade >= 90:
return 'A'
elif numeric_grade >= 80:
return 'B'
elif numeric_grade >= 70:
return 'C'
elif numeric_grade >= 60:
return 'D'
else:
return 'F'
def find_highest(grades):
"""Find the highest grade in a list."""
if not grades:
return None
return max(grades)
def find_lowest(grades):
"""Find the lowest grade in a list."""
if not grades:
return None
return min(grades)
# Module-level constants
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90Now create another file to use this module:
# student_report.py
import grade_calculator
# Student's test scores
test_scores = [85, 92, 78, 88, 95]
# Calculate statistics
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
# Generate report
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
# Check for honor roll
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
print("Status: Passing")
else:
print("Status: Needs Improvement")Output:
Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: PassingModule Documentation
Notice the docstring at the top of grade_calculator.py. This module-level docstring describes what the module does. It appears when someone uses help():
import grade_calculator
help(grade_calculator)This displays the module documentation, including the module docstring and all function docstrings. Good documentation makes your modules easier to use.
Module-Level Variables and Constants
Modules can contain variables that are shared across all uses of the module. These are often used for configuration or constants:
# config.py
"""Application configuration settings."""
# Database settings
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
# Application settings
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800 # seconds
DEBUG_MODE = False
# File paths
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
# Feature flags
ENABLE_CACHING = True
ENABLE_LOGGING = TrueUsing configuration from a module:
# app.py
import config
def connect_database():
"""Connect to the database using config settings."""
print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
print(f"Database: {config.DB_NAME}")
if config.DEBUG_MODE:
print("DEBUG: Connection details logged")
def check_login_attempts(attempts):
"""Check if login attempts exceed the limit."""
if attempts >= config.MAX_LOGIN_ATTEMPTS:
print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
return False
return True
connect_database()
print(check_login_attempts(2)) # Output: True
print(check_login_attempts(4)) # Output: Too many attempts! Maximum is 3Output:
Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3Important: Module-level variables are shared across all imports. If you modify a module variable, the change affects all code using that module:
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}") # Will be True!This behavior can be useful but also surprising. Be cautious when modifying module-level variables.
Private Names in Modules
By convention, names starting with an underscore are considered private or internal to the module:
# user_manager.py
"""Module for managing user accounts."""
# Private helper function
def _validate_email(email):
"""Internal function to validate email format."""
return '@' in email and '.' in email
# Public function
def create_user(username, email):
"""Create a new user account."""
if not _validate_email(email):
return None
user = {
'username': username,
'email': email,
'active': True
}
return user
# Private constant
_MAX_USERNAME_LENGTH = 20
# Public constant
MIN_PASSWORD_LENGTH = 8When you use from user_manager import *, private names (those starting with underscore) are not imported. However, you can still access them explicitly if needed:
import user_manager
# Public function - intended for use
user = user_manager.create_user("alice", "alice@example.com")
# Private function - can access but shouldn't rely on it
# (it might change in future versions)
is_valid = user_manager._validate_email("test@test.com")The underscore prefix is a signal to other programmers: "This is an implementation detail. Don't depend on it staying the same."
22.5) Understanding Packages and __init__.py
As projects grow, you'll want to organize multiple related modules into a package. A package is a directory containing Python modules and a special __init__.py file.
What Is a Package?
A package is a way to organize multiple modules into a hierarchical structure. Think of it as a folder that contains Python files, where the folder itself can be imported.
Here's a simple package structure:
myproject/
main.py
utilities/
__init__.py
text.py
math.py
file.pyIn this structure, utilities is a package containing three modules: text, math, and file. The __init__.py file (which can be empty) tells Python that utilities is a package.
Creating a Simple Package
Let's create a package for data processing. First, create this directory structure:
data_tools/
__init__.py
validators.py
formatters.pyCreate validators.py:
# data_tools/validators.py
"""Data validation functions."""
def is_valid_email(email):
"""Check if email has basic valid format."""
return '@' in email and '.' in email.split('@')[1]
def is_valid_phone(phone):
"""Check if phone number has valid format (simple check)."""
digits = ''.join(c for c in phone if c.isdigit())
return len(digits) == 10
def is_positive_number(value):
"""Check if value is a positive number."""
try:
return float(value) > 0
except (ValueError, TypeError):
return FalseCreate formatters.py:
# data_tools/formatters.py
"""Data formatting functions."""
def format_phone(phone):
"""Format phone number as (XXX) XXX-XXXX."""
digits = ''.join(c for c in phone if c.isdigit())
if len(digits) != 10:
return phone
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
def format_currency(amount):
"""Format number as currency."""
return f"${amount:,.2f}"
def format_percentage(value, decimals=1):
"""Format number as percentage."""
return f"{value * 100:.{decimals}f}%"Create an empty __init__.py:
# data_tools/__init__.py
"""Data processing tools package."""Importing from Packages
Now you can import from the package in several ways:
# Method 1: Import the module from the package
import data_tools.validators
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}") # Output: Email valid: True
# Method 2: Import specific module with from
from data_tools import formatters
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}") # Output: Formatted phone: (123) 456-7890
# Method 3: Import specific functions
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
print(is_valid_phone("555-1234")) # Output: False (not 10 digits)
print(format_currency(1234.56)) # Output: $1,234.56The __init__.py File
The __init__.py file serves two purposes:
- Marks the directory as a package: Python recognizes directories with
__init__.pyas packages - Package initialization code: Code in
__init__.pyruns when the package is first imported
The __init__.py file can be empty, but it can also contain code to make the package easier to use:
# data_tools/__init__.py
"""Data processing tools package."""
# Import commonly used functions into package namespace
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
# Package version
__version__ = '1.0.0'
# Package-level constant
DEFAULT_CURRENCY_SYMBOL = '$'Now users can import directly from the package:
# Instead of: from data_tools.validators import is_valid_email
# You can write:
from data_tools import is_valid_email, format_currency
print(is_valid_email("test@test.com")) # Output: True
print(format_currency(99.99)) # Output: $99.9922.6) The __name__ Variable and if __name__ == "__main__": Running a File as a Script
Python files can serve two purposes: they can be imported as modules or run as standalone scripts. The special __name__ variable helps you write code that works well in both situations.
Understanding __name__
Every Python module has a built-in variable called __name__. Python sets this variable differently depending on how the file is being used:
- When imported:
__name__is set to the module's name - When run directly:
__name__is set to"__main__"
Let's see this in action. Create a file called demo_name.py:
# demo_name.py
print(f"The __name__ variable is: {__name__}")Now run it directly:
python demo_name.pyOutput:
The __name__ variable is: __main__Now import it from another file:
# test_import.py
import demo_nameOutput:
The __name__ variable is: demo_nameWhen you run demo_name.py directly, Python sets __name__ to "__main__". When you import it, Python sets __name__ to the module name ("demo_name").
The if __name__ == "__main__": Pattern
This behavior lets you write code that only runs when the file is executed directly, not when it's imported. This is done with the pattern:
if __name__ == "__main__":
# Code here runs only when file is executed directly
passHere's why this is useful. Create math_utils.py:
# math_utils.py
"""Utility functions for mathematical operations."""
def calculate_area(radius):
"""Calculate the area of a circle."""
return 3.14159 * radius ** 2
def calculate_circumference(radius):
"""Calculate the circumference of a circle."""
return 2 * 3.14159 * radius
# Test code - runs only when file is executed directly
if __name__ == "__main__":
print("Testing math_utils functions...")
test_radius = 5
area = calculate_area(test_radius)
circumference = calculate_circumference(test_radius)
print(f"Radius: {test_radius}")
print(f"Area: {area:.2f}")
print(f"Circumference: {circumference:.2f}")When you run this file directly:
python math_utils.pyOutput:
Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42But when you import it:
# use_math_utils.py
import math_utils
# The test code doesn't run!
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}") # Output: Area of circle: 314.16The test code in the if __name__ == "__main__": block doesn't execute during import. This lets you include test code, examples, or demonstrations in your modules without affecting code that imports them.
Common Uses of if __name__ == "__main__":
Testing and Demonstrations
Include examples showing how to use your module:
# string_tools.py
def reverse_string(text):
"""Reverse a string."""
return text[::-1]
def count_vowels(text):
"""Count vowels in text."""
vowels = 'aeiouAEIOU'
return sum(1 for char in text if char in vowels)
if __name__ == "__main__":
# Demonstration code
sample = "Hello, World!"
print(f"Original: {sample}")
print(f"Reversed: {reverse_string(sample)}")
print(f"Vowels: {count_vowels(sample)}")In this chapter, we've learned how to organize Python code using modules and packages. We explored how the import system works, different ways to import code, and how to create our own modules and packages. We also learned about the __name__ variable and the if __name__ == "__main__": pattern that lets files work both as importable modules and standalone scripts.
These organizational tools become increasingly important as your programs grow. In the next chapter, we'll explore how to use functions as data and apply simple functional programming techniques, building on the solid foundation of code organization we've established here.