Modules in Python
Imagine you're writing a large program that gradually turns into hundreds or even thousands of lines of code. Keeping everything in one file becomes inconvenient — the code becomes difficult to maintain and it's hard to find the parts you need. This is where modules come to the rescue! 📦
What is a module?
A module in Python is simply a file with a .py extension, containing Python code (functions, classes, variables), which can be imported and used in other programs.
Modules help:
- Organize related code into separate files
- Reuse code in different programs
- Avoid name conflicts
- Make code more readable and maintainable
Creating your own module and importing it
Creating a module in Python is very simple — just write code in a file with a .py extension. Let's create mymath.py and place a few main-scripts next to it that show different ways to wire it in:
1# Import the whole module2import mymath3 4# All names live inside the mymath namespace.5 6result = mymath.add(5, 3)7print(f"5 + 3 = {result}") # 5 + 3 = 88 9area = mymath.PI * 5 ** 210print(f"Area of a circle with radius 5: {area}")11 12# Area of a circle with radius 5: 78.5397513 14 The tree on the left shows our mymath.py module and four main_*.py files, each demonstrating one import style. Switch tabs to compare them. Below is a short note on when each is the right fit.
Importing the entire module
The most direct option — import mymath (see main_basic.py). All names stay inside the module and are accessed via the prefix: mymath.add, mymath.PI. Useful when you need many functions from a single module and want their origin to remain visible.
Importing specific elements
from mymath import add, multiply (see main_specific.py). Pull in only what you need and use it without the prefix. Good for a couple of frequently called functions; bad once a hundred lines later you forget where add came from.
Importing with renaming
import mymath as mm (see main_aliased.py). Necessary when the module name is long (numpy as np, pandas as pd — those exact cases) or clashes with a local variable.
Importing all elements
from mymath import * (see main_star.py). Pulls everything into the current namespace. Usually a bad idea in normal code: the source of names is lost and conflicts are easy. It's fine in REPL sessions and sometimes in tests.
Module structure
A good module typically contains the following blocks, in order:
- Documentation — a triple-quoted string at the very top of the file describing the module's purpose.
- Imports — first the standard library, then third-party packages, then your own modules.
- Constants — global values that don't change at runtime.
- Classes — class definitions.
- Functions — function definitions.
- Execution code — an if __name__ == "__main__": block to run the module as a standalone program (covered below).
Look at our mymath.py: documentation is already on the first line, the constant PI is in place, then the functions. There are no imports (the module is self-contained), and no classes (arithmetic doesn't need them). The "execution code" block we'll add when we get to __name__.
Packages
Once mymath.py outgrows a single file (say, several distinct families of operations appear), you can split it across files and bundle them into a package. A package in Python is a directory with an __init__.py file. When you write import mathlib, Python sees that __init__.py and treats the directory as an importable package.
Let's turn our module into a package: split the functions by topic and assemble the public interface in __init__.py.
1"""mathlib: a package of mathematical functions."""2 3**version** = "0.1"4 5# Re-export so users can write from mathlib import add6 7from mathlib.basic import PI, add, subtract8from mathlib.advanced import multiply, divide9 What changed:
- There are four files now, but from the outside it's still "one math module" — the user keeps writing from mathlib import add, just like before with mymath.
- mathlib/__init__.py controls what counts as the public API: any name re-exported via from .basic import ... and from .advanced import ... becomes accessible as mathlib.add.
- Inside the package, modules can reference each other through relative imports: from . import basic (same directory), from .. import other (parent directory).
If the package keeps growing, you can split it into subpackages — for example mathlib/stats/ with its own __init__.py and statistics modules. Same idea, one level deeper.
Special module variables
In Python, modules have several special variables:
The __name__ variable: module as a program vs as a dependency
Every Python module has a __name__ variable. When the module is imported, it holds the module's name ("mymath"). When the module is run directly (python mymath.py), Python sets it to the special value "__main__". That lets you write a "do this only on direct run" block inside the module:
1"""Module with mathematical functions."""2 3PI = 3.141594 5def add(a, b):6return a + b7 8def divide(a, b):9if b == 0:10raise ValueError("Division by zero is not possible")11return a / b12 13# This block runs only on direct execution:14 15# python mymath.py16 17# When imported (import mymath) from another file, it stays silent.18 19if **name** == "**main**":20print(f"PI = {PI}")21print(f"add(2, 3) = {add(2, 3)}")22 Run python mymath.py and you'll see the if-block output. Run python main.py and that block stays silent — you only see 8 from the call inside main.py.
The __all__ variable: what ends up in from module import *
By default, from mymath import * pulls in every name that doesn't start with an underscore. To pin down the module's public API explicitly, add an __all__ variable with the list of exported names.
1"""Module with mathematical functions."""2 3# Only these names will be pulled in by from mymath import *4 5**all** = ["PI", "add"]6 7PI = 3.141598_INTERNAL = "should not leak out"9 10def add(a, b):11return a + b12 13def subtract(a, b):14return a - b15 16def _round_helper(value):17"""Internal helper, not for public use."""18return round(value, 2)19 from mymath import * brought in only PI and add — the names listed in __all__. The rest (subtract, _INTERNAL, _round_helper) stay inside the module and remain accessible only via explicit imports: from mymath import subtract.
Best practices when working with modules
-
One module — one responsibility: Each module should be responsible for one specific functionality.
-
Clear module names: Use descriptive but concise names (e.g., data_processing.py, user_interface.py).
-
Documentation: Each module should begin with documentation describing its purpose.
-
Imports at the beginning of the file: All imports should be at the beginning of the module.
-
Explicit imports: Prefer from module import specific_thing over from module import *.
-
Private names: Begin names of "private" functions and variables with an underscore (_private_function).
-
Module testing: Use the if __name__ == "__main__": block for testing the module.
Understanding Check
Let's check how well you've understood the topic of modules:
What will be output when running main.py from the project below?
1import test_module2 3print(test_module.hello())4 Remember, modules and packages help apply the "Don't Repeat Yourself" (DRY) principle and make your programs more professional. Organize your code wisely, and it will be much easier for you to develop it in the future! 💪
