Skip to content

Python Modules

Modules are a fundamental way to organize Python code. They allow you to group related functions, classes, and variables into separate files, promoting code reusability, maintainability, and logical structure. A module is essentially a Python file (.py) containing Python definitions and statements.

Think of modules as toolboxes. Instead of having all your tools scattered everywhere, you group related tools (like wrenches or screwdrivers) into specific boxes. When you need a specific tool, you grab the right toolbox (import the module) and then pick the tool you need.

Let’s create a module named helpers.py:

helpers.py
"""A simple module with helper functions."""
PI = 3.14159
def greet(name):
"""Greets the person with the given name."""
print(f"Hello, {name}!")
class Calculator:
"""A basic calculator class."""
def add(self, x, y):
return x + y

To use the definitions from a module in another Python file (e.g., main.py), you use the import statement. The basic syntax is:

import module_name
# Or import multiple modules
import module1, module2
# Or preferred style (one import per line)
import module_one
import module_two

When Python encounters import helpers, it searches for helpers.py (or other module types) in its search path. Once found, it executes the module’s code (if not already imported) and makes its definitions available via the module name prefix.

Example (main.py in the same directory as helpers.py):

main.py
import helpers
helpers.greet("Alice")
print(f"The value of PI is approximately: {helpers.PI}")
calc = helpers.Calculator()
result = calc.add(5, 3)
print(f"5 + 3 = {result}")

Running python main.py produces:

Hello, Alice!
The value of PI is approximately: 3.14159
5 + 3 = 8

A module is loaded and executed only once, the first time it is imported during a program’s execution. Subsequent imports of the same module simply retrieve the already loaded module object.

This statement allows you to import specific names (functions, classes, variables) directly into the current script’s namespace, avoiding the need for the module prefix.

from module_name import name1
# Or import multiple names
from module_name import name1, name2
# Or import and rename
from module_name import name1 as alias_name

Example:

main_from.py
from helpers import greet, PI
from helpers import Calculator as Calc # Renaming
greet("Bob")
print(f"PI: {PI}")
my_calc = Calc()
print(f"10 + 5 = {my_calc.add(10, 5)}")

This avoids prefixing with helpers. but introduces the imported names directly into the current scope. Be mindful of potential name collisions.

You can import all public names (those not starting with an underscore _) from a module using:

from module_name import *

Warning: This practice is generally discouraged in production code. It imports potentially many names into the current namespace, making it unclear where a specific name originated and increasing the risk of name conflicts. It’s acceptable in interactive sessions or sometimes within specific module patterns (__init__.py).

When you import my_module, Python searches for it in this order:

  • The directory containing the input script (or the current directory if interactive).
  • Directories listed in the PYTHONPATH environment variable (if set). The format is platform-dependent (e.g., colon-separated on Unix/macOS, semicolon-separated on Windows).
  • Installation-dependent default paths (usually within the Python installation’s site-packages directory).

The search path is accessible programmatically via sys.path, which is a list of strings.

import sys
print(sys.path)

PYTHONPATH is an environment variable you can set to add extra directories where Python will look for modules. Its syntax mirrors the system’s PATH variable.

Example (Linux/macOS):

Terminal window
export PYTHONPATH="/home/user/my_python_libs:/another/path"

Example (Windows Command Prompt):

Terminal window
set PYTHONPATH=C:\Users\user\my_python_libs;D:\other_path

While PYTHONPATH can be useful, managing dependencies with virtual environments and standard package installation (pip) is the preferred modern approach.

A namespace is a mapping from names to objects (like a dictionary). Python uses namespaces to avoid name collisions.

Python follows the LEGB rule for scope resolution: Local -> Enclosing function locals -> Global -> Built-in.

To modify a global variable from within a function, you must use the global keyword. To modify a variable in an enclosing function’s scope (in nested functions), use the nonlocal keyword (Python 3+).

x = 100 # Global
def outer_func():
y = 20 # Enclosing local
def inner_func():
nonlocal y # Modify enclosing scope variable
global x # Declare intent to modify global variable
z = 5 # Local
y += 5
x += 1
print(f"Inner: x={x}, y={y}, z={z}")
inner_func()
print(f"Outer: x={x}, y={y}")
outer_func()
print(f"Global: x={x}")

The built-in dir() function, when called without arguments, returns a list of names in the current local scope. When called with an object (like a module) as an argument, it returns a sorted list of names defined by that object (its attributes, methods, etc.).

import math
print(dir(math)) # List names in the math module
my_var = 10
def my_func(): pass
print(dir()) # List names in the current (global) scope

Output might include special names like __name__ (module’s name) and __file__ (path to the module file).

These built-in functions return dictionaries representing the global and local namespaces, respectively.

Modules are typically loaded only once. If you modify a module’s source file while your main program is running (common during interactive development or long-running applications), the changes won’t be reflected automatically. To force a reload of an already imported module, use the importlib.reload() function (note: reload was a built-in in Python 2, but moved to importlib in Python 3).

import importlib
import helpers
# ... code using helpers ...
# Assume helpers.py was modified
importlib.reload(helpers)
# Now use the updated version of helpers
helpers.greet("Updated User")

Use reload with caution, as it can lead to inconsistencies if not managed carefully, especially with complex dependencies or stateful objects.

Packages provide a way to structure Python’s module namespace by using “dotted module names”. A package is essentially a directory containing Python modules and potentially other sub-packages. To be recognized as a regular package (prior to Python 3.3, and still common practice), the directory must contain a file named __init__.py (which can be empty).

Example Structure:

sound/
__init__.py
formats/
__init__.py
wavread.py
aiffread.py
...
effects/
__init__.py
echo.py
surround.py
...
filters/
__init__.py
equalizer.py
...

You can import modules from packages using dot notation:

import sound.effects.echo
sound.effects.echo.apply_echo(...)
from sound.formats import wavread
wavread.read_wavfile(...)
from sound.filters.equalizer import Equalizer
eq = Equalizer()

The __init__.py file serves several purposes:

Example (sound/effects/__init__.py):

sound/effects/__init__.py
print("Initializing sound effects package...")
# Make echo and surround available directly via 'from sound.effects import ...'
from . import echo
from . import surround
# Control 'from sound.effects import *'
__all__ = ["echo", "surround"]

With this __init__.py, you could do:

import sound.effects
sound.effects.echo.apply_echo(...) # Access via sub-module

Note: Python 3.3 introduced Implicit Namespace Packages (PEP 420), which allow creating packages without an __init__.py file, primarily useful for splitting a single large package across multiple directories.