pyguides

Working with Python Modules and Imports

Every Python file pulls double duty: it is both an executable script and an importable module. Splitting programs across multiple files keeps each piece small enough to reason about, and Python’s import machinery lets you pull those pieces together without ceremony. A single import statement runs the target file once, caches the result, and hands you a namespace containing every function, class, and variable defined at the top level.

This guide covers creating your own modules, controlling what Python finds when it searches for them, choosing among the various import syntaxes, and tapping into the standard library, a sprawling collection of built-in tools that ships with every Python installation.

What Is a Module?

A module is simply a Python file containing definitions and statements. The filename (without the .py extension) becomes the module’s name. When you import a module, Python executes all the code in that file and makes its contents available.

Create a file called greeting.py with this content:

def say_hello(name):
    return f"Hello, {name}!"

def say_goodbye(name):
    return f"Goodbye, {name}!"

Drop into a neighbouring file or an interactive session, and a single import gives you the whole module. Python executes the file top to bottom on first import, then stashes the resulting module object in a cache; subsequent imports pull from that cache instantly instead of running the file again.

import greeting

message = greeting.say_hello("Alice")
print(message)  # Hello, Alice!

The module name (greeting) becomes a namespace that holds all the definitions from that file. This keeps your code organized and prevents name conflicts. When you write greeting.say_hello, you are accessing the function through the module’s namespace, which makes the origin of every name explicit and traceable.

The Import Statement

Python offers several ways to import modules and their contents. Each approach has its use cases.

The Basic Import

The most common form imports the entire module:

import math

result = math.sqrt(16)
print(result)  # 4.0

Keeping the module prefix preserves the namespace as-is. Every call site wears its dependency on its sleeve: math.sqrt leaves no ambiguity about which library provided the square-root function. That traceability pays dividends during code review and long-term maintenance, when someone who last saw the file six months ago needs to understand where each name entered scope.

Importing Specific Items

Use from ... import to bring specific names directly into your namespace:

from math import sqrt, pi

result = sqrt(16)
print(result)  # 4.0
print(pi)      # 3.141592653589793

Pulling in only what you need shortens the code at the cost of losing the prefix that identifies each name’s origin. When a module exposes dozens of functions and you only use two or three, the trade-off usually favours the targeted import: fewer keystrokes, less visual noise, and the import line itself documents exactly which APIs you depend on.

Aliasing

You can rename imported items using as. This helps avoid name conflicts or create shorter names:

import numpy as np
import tensorflow as tf

arr = np.array([1, 2, 3])

Community-standard aliases solve a naming problem: popular packages have long identifiers that would drown your code if you typed the full name at every call. Adopting conventions like np for NumPy and tf for TensorFlow makes your module instantly familiar to anyone who works in the same ecosystem.

The Star Import

You can import everything from a module using from module import *:

from math import *

result = sqrt(16)
print(pi)

Avoid this in production code. It fills your namespace with unknown names, making it hard to track where things come from. It also silently overwrites existing names. Use it only for quick interactive experiments.

The Module Search Path

When you write import something, Python needs to find the file. It searches in a specific order, stored in sys.path:

  1. The current directory (or the directory containing the script)
  2. Directories listed in the PYTHONPATH environment variable
  3. The standard library directories
  4. Site packages (where third-party libraries are installed)

You can see your current search path:

import sys

for path in sys.path:
    print(path)

Knowing the search order helps with two common debugging scenarios. First, when an import fails, confirming that the file lives in one of these directories narrows down the cause. Second, realising that local files shadow standard library modules by name prevents confusing bugs where your own typing.py or json.py gets imported instead of the real thing.

Running Modules as Scripts

Every Python file has a special variable called __name__. When you run a file directly, __name__ equals "__main__". When you import it, __name__ equals the module name.

This lets you use the same file as both a module and a standalone script:

# greeting.py
def say_hello(name):
    return f"Hello, {name}!"

if __name__ == "__main__":
    # This runs when the file is executed directly
    print(say_hello("World"))

When you run a Python file directly from the command line, the interpreter sets the special name variable to the string main, which triggers the guarded block. This design lets you include test code or a command-line interface inside any module without it executing when the module is imported elsewhere.

Run it directly:

python greeting.py
# Output: Hello, World!

Import it from another file. Running the file directly executes the guarded block and prints the greeting. Importing it, on the other hand, skips that conditional and gives you access to the say_hello function without triggering any side effects. This is the standard technique for making a module simultaneously useful as a library and as a standalone tool.

import greeting
print(greeting.say_hello("Alice"))
# Output: Hello, Alice!

Separating script behaviour from import behaviour through the name-main guard is a habit worth building early. Nearly every substantial Python codebase uses this mechanism to let a single file function as both a library and a command-line entry point, so internalising the pattern now saves refactoring later. Even inside the guarded block, calling through the module prefix keeps the namespace orderly regardless of invocation mode.

Exploring Module Contents

The dir() function lists everything defined in a module:

import math

print(dir(math))
# ['__doc__', '__loader__', '__name__', '__package__', 
#  'acos', 'acosh', 'asin', 'asinh', 'atan', ...]

Skimming the output of dir() is the fastest way to orient yourself inside a module you have never used before. Public functions like sqrt and acos sit alongside dunder attributes like __doc__ that Python uses internally; scanning past the double-underscore names reveals the actual API surface.

Packages: Directories of Modules

A package is a directory containing an __init__.py file, along with Python modules. Packages let you organize related modules into a hierarchy.

Create this structure:

my_package/
    __init__.py
    utils.py
    helpers/
        __init__.py
        data.py

The tree shown above defines a two-level package where my_package holds a utility module and a helpers subpackage. The __init__.py markers tell Python to treat these directories as importable units; when left empty, they serve as simple signposts, but they can also execute setup logic or re-export names from nested modules to flatten the public API.

Each __init__.py can be empty or contain initialization code:

# my_package/__init__.py
from .utils import process_data
from .helpers.data import load_file

__all__ = ["process_data", "load_file"]

Dot-prefixed imports search within the package rather than across the entire module path. The __all__ list declares the package’s public contract (everything outsiders should import directly), which lets you reorganise internal submodules without breaking callers who only touch the advertised surface.

from my_package import process_data
from my_package.helpers.data import load_file

Dotted imports mirror the filesystem, so navigating a codebase feels natural. Typing from my_package.helpers.data import load_file causes Python to walk the directory tree, executing init files at each level and resolving the target module at the end of the chain.

# my_package/helpers/__init__.py
from .data import load_file, save_file

__all__ = ["load_file", "save_file"]

Re-exporting through init files keeps your public interface tidy. Callers import from the top-level package without worrying about the internal module graph, which means you can shuffle submodule boundaries during a refactor and nobody’s code breaks as long as the advertised names still resolve correctly.

The Standard Library at Your Fingertips

Python ships with a massive standard library. You do not need to install anything extra to work with files, dates, web data, compression, and much more.

Here are some commonly used modules:

import os       # File and directory operations
import json     # JSON parsing and serialization
import datetime # Date and time handling
import re       # Regular expressions
import collections  # Specialized container datatypes
import itertools    # Iterator functions

Python’s standard library covers an enormous surface area, and reaching for it before installing a third-party package often pays off. Batteries like pathlib, dataclasses, and functools can replace entire dependencies with built-ins that undergo the same rigorous testing and maintenance cadence as the language core itself.

Common Import Patterns

Here is what professional Python code typically looks like:

# Standard library imports
import os
import sys
import json
from pathlib import Path

# Third-party imports
import requests
from flask import flash

# Local application imports
from .models import User
from .utils import format_date

Grouping imports into these three tiers with blank lines between them is a convention enforced by formatters like isort and Ruff. Sticking to it makes dependency auditing straightforward: glance at the top of any file and you can instantly tell which standard library, third-party, and local modules it needs.

Next Steps

You now understand how to organize code into modules and packages, and how to import what you need. These skills let you build larger applications without everything getting tangled together.

Continue your learning journey with the next tutorial in the series: classes and objects, where you will learn about object-oriented programming in Python.

See Also