Importing Built-in Modules in Python

Every non-trivial Python program relies on python import module statements to pull in functionality beyond what's built directly into the language's core syntax. This article covers python built-in modules — the ones bundled with every Python installation — the full range of import syntax, how Python actually locates a module when you import it, and the common ModuleNotFoundError causes worth knowing how to diagnose.

Introduction: What Modules Are and Why Import Them

A module is simply a .py file (or, for many built-in modules, a compiled equivalent written in C) containing reusable functions, classes, and variables — a way of organizing and sharing code across files and projects, rather than rewriting the same logic everywhere it's needed.

Three categories worth distinguishing

  • Built-in modules — bundled directly with Python itself: math, os, random, json, datetime, and many more. This article's focus.

  • Third-party modules — installed separately via pip (requests, pandas, numpy) — not included with Python by default.

  • Your own modules.py files you write yourself, importable elsewhere in your own project.

Why built-in modules matter

Built-in modules require no installation whatsoever — they're part of every standard Python installation, and importable the moment you have Python running at all, with zero setup beyond the import statement itself.

Basic Import Syntax

The standard form: import module_name
import math

print(math.pi)          # 3.141592653589793
print(math.sqrt(16))     # 4.0

import math brings the entire math module in, accessed through dot notation — math.pi, math.sqrt(). Everything the module provides stays namespaced under math., which avoids naming collisions with anything else in your own code.

Importing specific names: from module import name
from math import pi, sqrt

print(pi)          # 3.141592653589793
print(sqrt(16))     # 4.0 — no 'math.' prefix needed

from module import name brings specific names directly into your current namespace, without requiring the module prefix. This is more concise for names you'll use constantly, but it does mean pi and sqrt now exist directly in your file's namespace — worth being mindful of, since it increases the chance of an accidental naming collision with something else in your own code (covered further in Section 3).

Renaming with as
import math as m

print(m.pi)   # 3.141592653589793
from datetime import datetime as dt

now = dt.now()

as lets you rename a module or an imported name — genuinely useful for shortening a long or awkward name, or for avoiding a collision with an existing name already in use elsewhere in your file. This is also the convention behind extremely common aliases you'll see everywhere in real code, like import numpy as np or import pandas as pd.

Import Variants and Common Pitfalls

The wildcard form: from module import *
from math import *

print(pi)          # works
print(sqrt(16))     # works

This imports every public name from the module directly into your namespace at once. It's discouraged for a genuinely important reason: it can silently overwrite existing names in your file without any warning at all, and it makes code considerably harder to read, since there's no way to tell, just by looking at a bare name like pi or sqrt, which module it actually came from.

from math import *

sum = 10   # accidentally shadows the built-in sum() function!
print(sum([1, 2, 3]))
# TypeError: 'int' object is not callable

While this particular example uses a built-in rather than something from math itself, it illustrates the exact risk wildcard imports amplify: a large batch of names entering your namespace all at once, any of which could silently collide with something you already had, with no explicit signal at the point of use to help you notice.

ModuleNotFoundError and its common causes
import maths   # typo — should be 'math'
# ModuleNotFoundError: No module named 'maths'

The most common causes of this error:

  • A typo in the module name — as shown above, the single most frequent cause.

  • Working in the wrong virtual environment — a third-party package installed in one virtual environment simply doesn't exist in a different one, even on the same machine.

  • Accidentally shadowing a built-in module — if you create a file named math.py in your own project directory, import math inside that same project can end up importing your file instead of the real standard-library math module, since Python's module search (covered in Section 4) checks your current directory before reaching the standard library. This produces a genuinely confusing error, since the module technically was found — just the wrong one, missing the functions or attributes you actually expected.

Inspecting a module with dir()
import math

print(dir(math))
# ['acos', 'acosh', 'asin', ..., 'pi', 'sqrt', 'tau', ...]

dir(module_name) lists every name a module provides — genuinely useful for quickly checking exactly what's available without needing to look up documentation. Calling dir() with no arguments at all shows the names currently defined in your own local namespace:

x = 5
y = "hello"
print(dir())
# [..., 'x', 'y'] — your own current names, alongside some standard internal ones

How Python Finds a Module: sys.path Basics

The lookup order

When you write import some_module, Python follows a specific search order:

  1. Check sys.modules — a cache of every module already imported in the current session. If it's already there, Python reuses it immediately, without re-importing.

  2. Check for a built-in module — modules compiled directly into the Python interpreter itself.

  3. Search sys.path — a list of directories Python searches, in order, for a matching .py file (or package).

What populates sys.path
import sys
print(sys.path)
# ['', '/usr/lib/python3.13', '/usr/lib/python3.13/site-packages', ...]

sys.path is built from several sources: the directory containing the script currently being run (or the current working directory, in interactive mode — represented by the empty string '' at the start of the list above), the PYTHONPATH environment variable if it's set, and your Python installation's default site-packages location, where pip-installed packages typically live.

A practical debugging tip

If you're getting a confusing ModuleNotFoundError, or importing what turns out to be the wrong module entirely (as in the shadowing example from Section 3), printing sys.path directly is a genuinely useful first diagnostic step:

import sys
for path in sys.path:
    print(path)

This shows you exactly which directories Python is actually searching, in the exact order it searches them — often immediately revealing why a module can't be found (a missing directory), or why the wrong version of a module is being imported (an unexpected directory earlier in the list than you assumed).

Best Practices for Clean Imports

Place imports at the top of the file

Python doesn't strictly require this — technically, an import statement is valid anywhere in your code, even inside a function or conditionally inside an if block. But the strong, near-universal convention is placing all imports at the very top of a file, immediately visible to anyone reading it, giving them an instant overview of exactly what external functionality that file actually depends on.

Prefer explicit imports over wildcards
# Preferred — explicit, self-documenting, no risk of silent collisions
import math
from datetime import datetime

# Discouraged — see Section 3 for why
from math import *

import module or from module import specific_name both keep it clear, at the point of use, exactly where a given name came from — a genuine readability and safety benefit that a wildcard import gives up entirely.

Organizing imports by category

A widely followed convention groups imports into three sections, in this order: standard library imports first, third-party imports second, local (your own project's modules) imports last — typically with a blank line separating each group:

# Standard library
import json
import os
from datetime import datetime

# Third-party
import requests
import numpy as np

# Local
from my_project.utils import calculate_total

This ordering isn't enforced by Python itself, but it's a strong, widely recognized convention (formalized by tools like isort) that makes it immediately clear, at a glance, which dependencies come from Python itself, which are external packages your project depends on, and which are part of your own codebase.

importlib.reload() for interactive development

If you're working interactively — in a REPL or a Jupyter-style notebook — and you edit a module's source file after already importing it, Python won't automatically pick up those changes; the cached version in sys.modules stays in effect. importlib.reload() forces Python to re-execute the module's code and refresh that cached version:

import importlib
import my_module

# ... edit my_module.py externally ...

importlib.reload(my_module)   # picks up the changes without restarting the whole session

A caution worth flagging: avoid reloading core modules like sys or builtins — these are foundational to the interpreter's own operation, and reloading them can produce genuinely unpredictable, hard-to-diagnose behavior. importlib.reload() is a genuinely useful convenience specifically for your own modules during active, iterative development — not something to reach for casually on modules you didn't write yourself.