0%

40- Introduction to Modules & Packages

Organize your code into files. Import functionality from other files. Build reusable libraries. Modules and packages are how Python projects scale.

You have written many functions and classes. Your code works. But one file is becoming too long. You are scrolling up and down constantly. You have utility functions that you want to use in multiple projects. It is time to split your code into modules. A module is a Python file containing code. A package is a collection of modules in a directory. Together, they allow you to organize code into logical, reusable units. You can import modules into other modules. You can create libraries that others can install and use. Python comes with a large standard library of modules. You have already used some: random, math, datetime. Now you will learn to create your own.

🕯️ Magic Note

Every Python file is a module. When you run python my_script.py, you are executing the __main__ module. The name of the module is the filename without the .py extension. This simple fact—that any file can be imported—makes Python incredibly flexible for code organization.

What is a Module?
A module is a Python file that contains definitions and statements. The filename is the module name with a .py extension. You can import functions, classes, and variables from a module into another file.

Python

# File: my_math.py (a module)

def add(a, b):

return a + b

def multiply(a, b):

return a * b

PI = 3.14159

# File: main.py (using the module)

# import my_math

#

# result = my_math.add(5, 3)

# print(result) # 8

# print(my_math.PI) # 3.14159

💡 Module names should be lowercase with underscores. Avoid dashes. Choose short, descriptive names: data_utils.py, file_handlers.py, validators.py.
Importing Modules
Python provides several ways to import modules. Each has its own use case.

Python

# Method 1: Import the whole module (recommended)

import math

print(math.sqrt(16)) # 4.0

# Method 2: Import specific items

from math import sqrt, pi

print(sqrt(25)) # 5.0

print(pi) # 3.141592653589793

# Method 3: Import with an alias

import numpy as np # Common alias for NumPy

# Often used with third-party libraries

# Method 4: Import everything (not recommended)

from math import *

# Pollutes the namespace, hard to track where names come from

🕯️ Magic Note

The import math statement loads the math module and creates a reference in the current namespace. The module’s contents are accessed via math.sqrt. The from math import sqrt brings sqrt directly into the current namespace. The star import (from math import *) brings all names that do not start with underscore. Avoid star imports in production code.

The import System and Module Search Path
When you import a module, Python searches for it in specific locations in order.
  • 1. The directory containing the script being run
  • 2. Directories in the PYTHONPATH environment variable
  • 3. Standard library directories
  • 4. Site-packages directory (third-party packages)

Python

import sys

print(sys.path) # List of directories Python searches for modules

# You can add directories to the search path

sys.path.append(“/path/to/my/modules”)

💡 Avoid modifying sys.path in production code. Instead, structure your project properly with packages and use relative imports. Or install your module using pip install -e . in development mode.
The __name__ Variable
Every module has a __name__ attribute. If the module is run directly, __name__ is “__main__”. If it is imported, __name__ is the module name.

Python

# File: my_math.py

def add(a, b):

return a + b

if __name__ == “__main__”:

# This code runs only when the script is executed directly

# Not when it is imported

print(“Testing my_math module”)

result = add(5, 3)

print(f”5 + 3 = {result}”)

🕯️ Magic Note

The if __name__ == “__main__”: guard is a standard pattern. It allows a file to be both a reusable module and a standalone script. You can put test code or example usage inside this block without it running when the module is imported.

Creating Your First Module
Let us build a practical utility module step by step.

Python

# File: string_utils.py

“””

Utility functions for string manipulation.

This module provides helper functions for working with strings.

“””

def reverse_string(text):

“””Return the reverse of a string.”””

return text[::-1]

def is_palindrome(text):

“””Check if a string is a palindrome.”””

cleaned = text.lower().replace(” “, “”)

return cleaned == cleaned[::-1]

def count_vowels(text):

“””Count the number of vowels in a string.”””

vowels = “aeiouAEIOU”

return sum(1 for char in text if char in vowels)

def capitalize_words(text):

“””Capitalize the first letter of each word.”””

return ” “.join(word.capitalize() for word in text.split())

if __name__ == “__main__”:

# Test code

test_string = “hello world”

print(f”Original: {test_string}”)

print(f”Reversed: {reverse_string(test_string)}”)

print(f”Is palindrome ‘racecar’? {is_palindrome(‘racecar’)}”)

print(f”Vowel count: {count_vowels(test_string)}”)

print(f”Capitalized: {capitalize_words(test_string)}”)

# File: main.py (using the module)

import string_utils

text = “A man a plan a canal panama”

print(string_utils.is_palindrome(text)) # True

What is a Package?
A package is a directory containing multiple modules. It must contain a special file called __init__.py (which can be empty). Packages allow hierarchical organization of code.

Python

# Directory structure:

# my_package/

# __init__.py

# math_utils.py

# string_utils.py

# file_utils.py

# File: my_package/__init__.py (can be empty)

# This file tells Python that this directory is a package

# File: my_package/math_utils.py

def add(a, b):

return a + b

# Usage in another file:

# from my_package import math_utils

#

# result = math_utils.add(5, 3)

💡 In Python 3.3 and later, __init__.py is not strictly required for packages (namespace packages). However, it is still good practice to include it, even if empty, to make the intent clear.
Importing from Packages
There are several ways to import from packages, similar to modules, but with dot notation.

Python

# Assuming this package structure:

# my_package/

# __init__.py

# math_utils.py

# string_utils.py

# Import a module from a package

import my_package.math_utils

my_package.math_utils.add(5, 3)

# Import a specific function from a module in a package

from my_package.math_utils import add

add(5, 3)

# Import a module with an alias

import my_package.math_utils as mu

mu.add(5, 3)

# Import everything from a module (not recommended)

from my_package.math_utils import *

Controlling What Gets Imported: __all__
You can control what is imported when someone writes from module import * by defining __all__.

Python

# File: my_package/math_utils.py

__all__ = [“add”, “multiply”, “PI”]

def add(a, b):

return a + b

def multiply(a, b):

return a * b

def subtract(a, b):

return a – b

PI = 3.14159

# Now, from my_package.math_utils import * will only import

# add, multiply, and PI. subtract will not be imported.

🕯️ Magic Note

The __all__ variable only affects star imports (from module import *). It does not affect other import forms. It is a promise to users: “These are the public names I intend you to use.”

Relative Imports
Inside a package, you can import sibling modules using relative imports with dots.

Python

# File: my_package/math_utils.py

def add(a, b):

return a + b

# File: my_package/calculator.py

# Relative import (import sibling module)

from .math_utils import add

# from .. import some_module (parent directory)

def calculate(a, b):

return add(a, b) * 2

⚠️ Relative imports only work inside a package. They cannot be used in scripts that are run directly (if __name__ == “__main__”). Use absolute imports for scripts and in most cases for clarity.
The __init__.py File
The __init__.py file is executed when the package is imported. It can set up package-level variables, import submodules, or define what gets exposed.

Python

# File: my_package/__init__.py

# This code runs when the package is imported

print(“Initializing my_package”)

# Import important names to the package level

from .math_utils import add, multiply

from .string_utils import reverse_string, is_palindrome

# Define what gets imported with “from my_package import *”

__all__ = [“add”, “multiply”, “reverse_string”, “is_palindrome”]

# Package-level variable

VERSION = “1.0.0”

# Now users can do:

# import my_package

# my_package.add(5, 3)

# print(my_package.VERSION)

🕯️ Magic Note

The __init__.py file can be empty, but it is often used to simplify the package interface. Instead of forcing users to write from my_package.math_utils import add, you can import add into the package’s __init__.py so they can write from my_package import add.

Installing Third-Party Packages with pip
Python has a vast ecosystem of third-party packages. You can install them using pip, the package installer for Python.

Bash

# Install a package

pip install requests

# Install a specific version

pip install requests==2.28.0

# Upgrade a package

pip install –upgrade requests

# Uninstall a package

pip uninstall requests

# List installed packages

pip list

# Install from a requirements file

pip install -r requirements.txt

Creating a requirements.txt File
A requirements.txt file lists all dependencies for a project. It allows others to install the exact same packages.

Text

# requirements.txt example

requests==2.28.0

numpy==1.21.0

pandas>=1.3.0

flask

Bash

# Generate requirements.txt from current environment

pip freeze > requirements.txt

# Install all packages from requirements.txt

pip install -r requirements.txt

Organizing a Project as a Package
Here is a typical structure for a Python project organized as a package.

Text

my_project/

├── my_package/ # The main package

│ ├── __init__.py

│ ├── core.py

│ ├── utils.py

│ └── subpackage/ # Subpackage

│ ├── __init__.py

│ └── helper.py

├── tests/ # Unit tests

│ ├── __init__.py

│ ├── test_core.py

│ └── test_utils.py

├── scripts/ # Executable scripts

│ └── run.py

├── requirements.txt # Dependencies

├── setup.py # For installing the package

└── README.md # Documentation

Common Mistakes with Modules and Packages
  • Creating circular imports (module A imports module B, module B imports module A)
  • Using from module import * in production code (pollutes namespace)
  • Name conflicts with standard library modules (naming your module math.py)
  • Forgetting the __init__.py file in packages (Python 3.3+ does not require, but still good practice)
  • Using relative imports in scripts run directly
  • Not using if __name__ == “__main__”: for test code
Check Your Understanding
  • How do you create a module?
  • What is the purpose of if __name__ == “__main__”:?
  • Create a package named shapes with modules circle.py and square.py, each containing an area() function.
  • What is the difference between import math and from math import sqrt?
  • Why should you avoid from module import *?
  • What does the __all__ variable do?

⚡ Whisper

A module is a file. A package is a folder. But these are not just technical details. They are ways to divide your code into meaningful pieces. One module for math utilities. Another for string helpers. Another for file handling. Each has a clear purpose. Each stands alone. Each can be tested, debugged, and improved without touching the others. When you import a module, you are not just loading code. You are making a promise: “This functionality is ready for use.” The module does not care where it is imported from. It does not care who called it. It just does its job and returns. This is modularity. This is reusability. This is how small scripts become large systems. Organize your code into modules. Group modules into packages. Share them across projects. Your future self will thank you. And so will the developers who come after you.

Related posts