Modules

A module is just a .py file whose top-level code and definitions become a reusable, importable namespace — the basic unit of code organization in Python.

As programs grow, you stop writing everything in one file. Python's answer to that is the module: any .py file is automatically a module, and any other file can import it to reuse its functions, classes, and variables. Modules are how Python code is organized, shared, and kept from turning into one giant script.

What Is a Module

A module is a file containing Python definitions and statements. The file name (minus the .py extension) becomes the module name. math, json, and os are modules from the standard library; a file you write named geometry.py is a module named geometry that anyone (including you) can import.

This file, once saved as geometry.py, is a complete module. Nothing special marks it as one — being a .py file is enough.

# geometry.py
PI = 3.14159

def area_circle(radius: float) -> float:
    return PI * radius ** 2

def area_square(side: float) -> float:
    return side ** 2

Why Modules Matter

Modules give you namespacing (two modules can each define a function called run without colliding), reuse (write logic once, import it everywhere), and organization (related code lives together, unrelated code lives apart). Without modules, every name in every file would compete in one global space.

The standard library itself is just a large collection of modules (and packages, covered in the next topic) — math, random, datetime, collections, and hundreds more.

Importing a Module

The import statement makes a module's namespace available under its own name. You then access its contents with dot notation.

geometry becomes a name in the importing file bound to the module object; its members are reached through it.

The full mechanics of import, from ... import, aliases, and the search path are covered in the Import System topic — this page focuses on what a module is and how it behaves once written.

import geometry

print(geometry.PI)
print(geometry.area_circle(2.0))

The Module Namespace

Every module has its own private namespace: a dictionary mapping the names defined at that file's top level to their objects. This namespace is exposed to Python as the module's __dict__, and every top-level def, class, and assignment in the file becomes an entry in it.

Because each module has its own namespace, a name defined inside a module never leaks into the importer's namespace on its own — you must go through the module object (or explicitly from module import name).

import geometry
print(list(geometry.__dict__.keys())[:5])
# includes '__name__', '__file__', ..., 'PI', 'area_circle', 'area_square'

Module Execution: Import Runs the File Once

Importing a module executes every top-level statement in that file, top to bottom, exactly once per process. Function and class bodies are compiled but not run until called; everything else — assignments, print calls, loops at module level — runs immediately during import.

The print in greetings.py fires once, during the first import, because Python caches modules after loading them (see the Import System topic for sys.modules).

# greetings.py
print('greetings module is loading')

def hello():
    return 'hi'

# main.py
import greetings
print('back in main')
import greetings  # no output: already imported, cached

__name__: How a File Knows Its Own Role

Every module has a built-in __name__ attribute. When a file is run directly (e.g. python geometry.py), Python sets __name__ to the string "__main__" for that file. When the same file is imported instead, __name__ is set to the module's own name ("geometry").

This is the mechanism behind the if __name__ == "__main__": guard, which gets its own dedicated topic — the short version is that it lets a file behave as both a runnable script and an importable module.

# geometry.py
print(__name__)

# running: python geometry.py  -> prints '__main__'
# importing: import geometry   -> prints 'geometry'

Writing Reusable Modules

A well-designed module has a clear, narrow purpose, avoids side effects at import time (no network calls, no file writes just from being imported), and exposes a small, deliberate public surface. Keep script-only behavior (parsing command-line arguments, printing results) behind a main guard so the module stays safely importable.

Importing stats does not print anything; running stats.py directly demonstrates it. Both work from the same file.

# stats.py — a reusable module
def mean(values: list[float]) -> float:
    return sum(values) / len(values)

def variance(values: list[float]) -> float:
    m = mean(values)
    return sum((x - m) ** 2 for x in values) / len(values)

if __name__ == '__main__':
    data = [1.0, 2.0, 3.0, 4.0]
    print('mean:', mean(data))
    print('variance:', variance(data))

Where Python Finds Your Module

To import geometry, Python must find a file (or package) named geometry somewhere on its module search path — by default this includes the directory of the script being run, plus the installed standard library and any installed third-party packages. A module in an unrelated directory is invisible unless that directory is added to the search path.

The exact rules — sys.path, PYTHONPATH, and how import resolves a name to a file — belong to the Import System topic, not this one.

Mental Model

Think of a module as a singleton object that Python builds by running a file top to bottom and collecting whatever names end up defined at the top level. import doesn't copy text into your file the way a C #include does — it runs the target file once, builds a module object holding its namespace, and hands you a reference to that object.

Quick Reference

A .py file           -> automatically a module
import module         -> runs file once, binds module object to name
module.name            -> access something defined in the module
module.__name__         -> 'module' when imported, '__main__' when run directly
module.__dict__          -> the module's namespace as a dict

Key Takeaways

Any .py file is a module; its filename (without .py) is the module name.

Importing a module runs its top-level code exactly once and caches the result.

Each module has its own namespace; names must be reached via the module object or an explicit from-import.

__name__ is "__main__" when run directly and the module's own name when imported.

Good modules avoid import-time side effects and guard script-only code behind if __name__ == "__main__":.

Top-level side effects (queries, prints, network calls) fire the instant the module is imported, surprising anyone who imports it just to reuse a function.

A local file that shadows a standard-library module name will be found first on the search path, silently breaking any code (including your own) that expected the real stdlib module.

What makes a .py file a module in Python?

Nothing special — every .py file is automatically usable as a module; the filename without .py becomes the module name.

How many times does a module's top-level code run if it is imported from three different files in the same program?

Once — Python caches the module after the first import and reuses it for subsequent imports in the same process.

What value does __name__ hold when a file is executed directly with python file.py?

Why should a module avoid doing real work (network calls, file writes) at the top level?

Because that work runs automatically the moment the module is imported, even if the importer only wanted one function from it.

What is a module's namespace, concretely?

A dictionary (exposed as module.__dict__) mapping every name defined at the file's top level to the object it refers to.

If module A defines a variable count = 0 and module B does import A, can B see count directly by writing count?

No — B must write A.count (or explicitly from A import count); a module's names don't leak into the importer's own namespace automatically.

Write a module temperature.py with a function to_fahrenheit(celsius) and a main guard that demonstrates it converting 100.0 when run directly, but prints nothing when imported.

Running python temperature.py prints 212.0. Doing import temperature from another file defines to_fahrenheit without printing anything, because the print is inside the __name__ == '__main__' guard.

How many times does 'loading counter' print, and what does the final print show?

'loading counter' prints once — the second import counter reuses the cached module instead of re-running the file. The final print shows 1, since increment() is called once, incrementing count from 0.

Explain, in one or two sentences, the difference between what happens when a file is run as a script versus imported as a module.

In both cases Python executes the file's top-level code top to bottom exactly once; the only difference is the value bound to __name__ — "__main__" when run directly, the module's own name when imported — which lets code branch on how the file is being used.

# counter.py
print('loading counter')
count = 0

def increment():
    global count
    count += 1
    return count

# main.py
import counter
import counter
print(counter.increment())