Creating Your Own Modules in Python

Everything covered in the previous article — importing math, os, json — works exactly the same way for code you write yourself. This article covers how to create python custom module files, import them into other scripts, and the if __name__ == "__main__": pattern, which is genuinely one of the most commonly misunderstood things in everyday Python code.

Introduction: Why Write Your Own Modules

As a program grows bigger, it may contain many lines of code — at some point, cramming everything into a single file becomes genuinely hard to navigate and maintain. Splitting related functions, classes, and variables into separate files keeps a project organized, and makes each individual piece easier to understand, test, and reuse.

A module is just a .py file

There's no special syntax, no magic decorator, no particular structure required to create a module. Any .py file is already a module, the moment it exists:

# mymath.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

That's it — mymath.py is now a fully valid, importable Python module.

A preview

This article covers creating a basic module, importing it from another file, the __name__ guard that lets a single file work both as a standalone script and as an importable module, and the natural next step once a project outgrows a single module: grouping several modules into a package.

Creating a Basic Module

A simple example
# mymath.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

Saving this as mymath.py creates a module containing three related functions — nothing more is needed for this file to be importable elsewhere.

Naming guidance: avoid colliding with the standard library

This connects directly to the module-shadowing pitfall covered in the earlier imports article: pick a module name that doesn't collide with an existing standard-library module. Never name a project file math.py, json.py, random.py, or similar — Python's module search checks your own project directory before it reaches the standard library, meaning import math inside that project would import your own file instead of the real, built-in math module, producing a genuinely confusing error the moment your code tries to use anything the standard math module actually provides.

What a module can contain

A module isn't limited to functions — it can hold classes, variables, and even runnable top-level code that executes immediately the moment the module is imported:

# config.py
DEFAULT_TIMEOUT = 30
API_VERSION = "v2"

class Settings:
    def __init__(self, timeout=DEFAULT_TIMEOUT):
        self.timeout = timeout

print(f"config module loaded, API version {API_VERSION}")   # runs on import

That final print() statement runs immediately the moment config is imported anywhere — worth keeping in mind, since any top-level code in a module (not tucked inside a function or the __name__ guard covered in Section 4) executes automatically at import time, not just when explicitly called.

Importing and Using a Custom Module

The simplest case: same directory

If your custom module sits in the same directory as the script importing it, Python's module search (covered in the earlier imports article) finds it automatically — no special configuration needed:

project/
├── mymath.py
└── main.py
Import styles
# main.py
import mymath

print(mymath.add(3, 5))        # 8
print(mymath.subtract(10, 4))   # 6
# main.py — alternative, importing specific names directly
from mymath import add, subtract

print(add(3, 5))        # 8
print(subtract(10, 4))   # 6

Both work exactly the same way they would for a built-in module — import mymath requires the mymath. prefix on every call; from mymath import add, subtract brings those specific names directly into main.py's own namespace.

A practical example
# mymath.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b
# main.py
from mymath import add, subtract, multiply

result1 = add(10, 5)
result2 = subtract(10, 5)
result3 = multiply(10, 5)

print(f"Sum: {result1}, Difference: {result2}, Product: {result3}")
# Sum: 15, Difference: 5, Product: 50

main.py uses mymath.py exactly the same way it would use any built-in module — the fact that you wrote mymath.py yourself, five minutes ago, makes no practical difference to how Python imports and uses it.

The if name == "main": Pattern

What name actually holds

Every Python module has a built-in variable called __name__, and its value depends entirely on how the file is being run:

  • If the file is executed directly (e.g., python mymath.py from the terminal), __name__ is set to the string "__main__".

  • If the file is imported by another script instead, __name__ is set to the module's own name — "mymath", in this case.

# mymath.py
print(f"__name__ is: {__name__}")

def add(a, b):
    return a + b

Running python mymath.py directly prints __name__ is: __main__. But importing it from main.py with import mymath instead prints __name__ is: mymath — the exact same file, the exact same line of code, producing a genuinely different value depending purely on how it was invoked.

Why this matters

This distinction lets you write code inside a module that only runs when the file is executed directly — never when it's imported elsewhere:

# mymath.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

if __name__ == "__main__":
    # this block only runs when mymath.py is executed directly
    print("Running mymath.py directly")
    print(add(2, 3))
    print(subtract(5, 2))

If another script does from mymath import add, the if __name__ == "__main__": block never runs at all — only add and subtract get imported, cleanly, with none of the demonstration code inside the guard executing as an unwanted side effect. But running python mymath.py directly does trigger that block, since __name__ is "__main__" in that specific case.

Practical uses

This pattern serves two genuinely common, related purposes:

  • Quick self-tests or example usage. You can add small demonstration or sanity-check code at the bottom of a module, useful for quickly verifying it works correctly when run on its own, without that code accidentally running (and potentially producing unwanted output, or unwanted side effects) every single time the module gets imported somewhere else.

  • Dual-purpose files. The same file can work both as a standalone script (run directly, performing some specific task) and as a reusable library (imported elsewhere, providing functions and classes for other code to use), with the guard cleanly separating "code that should run when this file is the program" from "code that should be available when this file is used by another program."

# data_processor.py
def process(data):
    return [x * 2 for x in data]

if __name__ == "__main__":
    # only runs when this file is executed directly, e.g. `python data_processor.py`
    sample_data = [1, 2, 3, 4, 5]
    print(process(sample_data))

Someone can run python data_processor.py directly to see it process the sample data as a quick demonstration, while another script can cleanly from data_processor import process and use just the function itself, with none of that demonstration code running unexpectedly in the background.

From Modules to Packages

The natural next step

As a project grows further, even splitting logic across several separate module files can eventually become unwieldy on its own — the natural next step is grouping several related modules together into a folder, forming what's called a package.

mypackage/
├── __init__.py
├── math_operations.py
├── string_operations.py
└── validators.py
The defining requirement

Formally, a package is a module that has a __path__ attribute — in practical terms, this means a directory containing other .py files, which Python recognizes as a package rather than just an ordinary folder. Historically, this required an __init__.py file inside the directory (even if that file was completely empty) to explicitly mark it as a package. Since Python 3.3, "namespace packages" (per PEP 420) can technically work without an __init__.py file at all — but including one remains extremely common practice, since it gives you a clear, explicit place to control exactly what the package exposes when imported, and unambiguously signals "this directory is a package" to both Python and to anyone reading the project's structure.

# mypackage/__init__.py
from .math_operations import add, subtract
from .string_operations import reverse_string
# elsewhere in your project
from mypackage import add, reverse_string
A real-world framing

This exact same pattern — a directory containing several related module files, grouped together as one importable unit — is precisely how much of Python's own standard library, and the vast majority of third-party libraries you pip install, are actually structured internally. A hypothetical sound package handling several different audio file formats might organize wav.py, mp3.py, and flac.py as separate modules inside a single sound package, each handling one format, but all importable together as sound.wav, sound.mp3, and so on. This directly connects back to everything covered in the earlier imports article — packages are simply modules organized into directories, following exactly the same import mechanics, sys.path resolution, and naming considerations already covered there, just applied at one additional level of organization as a project grows beyond what a single file, or even a handful of loose files, can comfortably hold.