Skip to content

Python 3 - Modules

Modules are a fundamental concept in Python for organizing code. They allow you to group related functions, classes, and variables into separate files, making your code more manageable, reusable, and understandable.

Essentially, a module is simply a Python file (.py) containing Python definitions and statements. The file name becomes the module name (without the .py extension).

Let’s create a module named greetings.py with a function:

greetings.py
def say_hello(name):
"""Prints a greeting message."""
print(f"Hello, {name}!")
# You could also define variables or classes here
PI = 3.14159

To use the code from a module in another Python file (or the interactive interpreter), you use the import statement.

import module_name

When Python encounters import greetings, it looks for greetings.py in a specific list of directories (the module search path). If found, Python executes the module’s code (if any top-level code exists) and makes its contents available through the module name.

main_script.py
import greetings # Import the greetings module
# Access functions/variables using dot notation
greetings.say_hello("Alice")
print(f"The value of PI from greetings module is: {greetings.PI}")

Running main_script.py produces:

Hello, Alice!
The value of PI from greetings module is: 3.14159

A module is loaded and executed only once per Python session, even if it’s imported multiple times. This prevents redundant initialization.

Alternatively, you can import specific names (functions, classes, variables) directly into the current script’s namespace using from...import.

from module_name import name1, name2

Example: Importing only the say_hello function:

main_script_from.py
from greetings import say_hello
# Now call the function directly
say_hello("Bob")
# PI is not imported directly, so this would cause an error:
# print(PI)

This makes the code slightly shorter but can lead to naming conflicts if the imported name already exists in your script.

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

from module_name import *

Warning: This is generally discouraged in production code. It imports potentially many names into your current namespace, making it unclear where a particular function or variable originated and increasing the risk of name collisions. Use it sparingly, perhaps only in interactive sessions for convenience.

Executing Modules as Scripts (if __name__ == "__main__":)

Section titled “Executing Modules as Scripts (if __name__ == "__main__":)”

Every Python module has a built-in variable called __name__. When a module is run directly as the main script (e.g., python my_module.py), its __name__ is set to the string "__main__". When the module is imported by another script, its __name__ is set to the module’s actual name (e.g., "my_module").

This allows you to write code within a module that only executes when the module is run directly, typically used for tests, examples, or making the module runnable.

greetings.py
def say_hello(name):
"""Prints a greeting message."""
print(f"Hello, {name}!")
PI = 3.14159
# This block runs ONLY when greetings.py is executed directly
if __name__ == "__main__":
print("Running greetings.py as the main script.")
say_hello("Direct Run User")
print(f"PI value is: {PI}")

If you run python greetings.py, you’ll see the output from the if block. If you import greetings into another script, that block won’t execute automatically.

When you import my_module, Python searches for my_module.py (or a package named my_module) in this order:

  • The directory containing the input script (or the current directory if interactive).
  • Directories listed in the PYTHONPATH environment variable (if set).
  • Installation-dependent default paths (e.g., .../site-packages within your Python installation or virtual environment).

The complete search path is available as a list of strings in sys.path (you need to import sys first).

Best Practice: For managing project-specific dependencies, using virtual environments (like venv or conda) is highly recommended. Virtual environments create isolated Python setups where you can install packages without affecting your global Python installation or other projects. This largely replaces the need to manually manage PYTHONPATH for typical development.

Namespaces and Scoping (Briefly Revisited)

Section titled “Namespaces and Scoping (Briefly Revisited)”

A namespace is a mapping from names to objects (like variables, functions). Modules create their own global namespace. When you access greetings.say_hello, you’re looking up say_hello in the namespace associated with the greetings module.

Function calls create local namespaces. Python follows the LEGB rule for finding names: Local -> Enclosing function locals -> Global (module-level) -> Built-in.

The global keyword is used inside a function to indicate that an assignment should modify a global variable instead of creating a new local one. The nonlocal keyword (used in nested functions) does similarly for names in enclosing function scopes.

#!/usr/bin/env python3
money = 2000 # Global variable
def add_money():
# To modify the *global* money, uncomment the next line:
# global money
money = 1 # Creates a *local* variable 'money' if 'global' isn't used
money = money + 1
print("Inside function (local money):", money)
print("Before calling function (global money):", money)
add_money()
print("After calling function (global money):", money) # Unchanged if 'global' wasn't used

The built-in dir() function, when called with a module object, returns a sorted list of names defined within that module.

#!/usr/bin/env python3
import math
content = dir(math)
print(content)
# Try with our module
import greetings
print("\nContents of greetings module:")
print(dir(greetings))

This is useful for exploring the contents of a module. It lists functions, classes, variables, and special attributes like __name__ and __file__.

As mentioned, modules are typically loaded only once. If you modify a module’s source file while your main script is running (common during interactive development or long-running applications), the changes won’t be reflected automatically.

In Python 3, the reload() function was moved from built-ins to the importlib module. You can use it to re-execute a module’s code and update its namespace.

import importlib
import greetings
# ... Make changes to greetings.py ...
# Reload the module
importlib.reload(greetings)
# Now calls will use the updated code from greetings.py
greetings.say_hello("After Reload")

Note: reload() is primarily intended for interactive debugging sessions. Relying on it in production code is often a sign of design issues. It’s generally better to restart the application or design for dynamic configuration loading.

For larger projects, you might want to organize related modules into a directory hierarchy. This is where packages come in. A package is simply a directory containing Python modules and a special (often empty) file named __init__.py.

The __init__.py file signals to Python that the directory should be treated as a package. It can also contain initialization code for the package or define what symbols are exported when using from package import * (via the __all__ list).

Consider this structure:

my_app/
__init__.py
main.py
utils/
__init__.py
strings.py
math_ops.py
models/
__init__.py
user.py

Here, my_app, utils, and models are packages because they contain __init__.py. You can import modules using dot notation:

# In my_app/main.py
import my_app.utils.strings # Method 1: Full path
from my_app.utils import math_ops # Method 2: Import submodule
from my_app.models.user import User # Method 3: Import specific class/function
# Using the imports
cleaned_text = my_app.utils.strings.clean(" some text ")
result = math_ops.add(5, 3)
new_user = User("admin")
# --- Relative Imports (within the package) ---
# In my_app/utils/strings.py, you might import math_ops:
# from . import math_ops # '.' means current package ('utils')
# from .. import main # '..' means parent package ('my_app')

Packages allow you to create well-structured, large applications by grouping related functionality and avoiding naming conflicts.