Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~35 min· topic 1 of 9

Topic 6.1

import: Using Code from Other Files

In one line

import loads another module and gives you a name for it. Python runs the module's file once, the first time it is imported, and keeps the result in sys.modules so that every later import is instant and shares the same module object.

Think of it like this

A school library. You do not copy every book into your bag. You ask the librarian for one by name. The first time anyone asks for a rare book, the librarian walks to the storage room, fetches it and puts it on the front shelf. After that, everyone who asks gets the same copy from the shelf, instantly. import is asking the librarian; sys.modules is the front shelf.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Module
A Python file (or a built-in) that other code can import. Its top-level names become its attributes.
import
The statement that finds a module, runs it the first time, and gives you a name for it.
Module object
The Python object that represents a loaded module. You use it with a dot: math.pi.
Namespace
A set of names and what they refer to. Every module has its own, so two modules can both have a total without clashing.
sys.modules
A dictionary of every module already loaded, keyed by name. It is Python's cache of finished imports.
Alias
A different name you choose with as, for example import numpy as np.
Standard library
The modules that come with Python itself, such as math, json and pathlib.
Third-party package
Code written by other people, installed from PyPI, such as numpy or pydantic.
Star import
from module import *. It copies every public name and is usually a bad idea.
Cache
A place where a finished result is kept, so the work is not done twice. sys.modules is the cache of imports.
Bytecode
The compact instructions Python turns your file into before it runs it (Topic 0.8).

Step by step

01Three ways to import the same thing

All three lines below load the math module. They only change which names you get. Notice that math and m are the same object under two names, just like two labels on one box.

from math import pi as PI combines both tricks: it copies one name out and renames it.

main.pywhole filepython
import math
import math as m
from math import sqrt, pi as PI

print(math.sqrt(16), m.sqrt(25), sqrt(36))
print(math is m)

02What happens on the first import

When Python meets import greeter, it does these steps in order. (1) Look in sys.modules for the key "greeter". If it is there, use it and stop. (2) Otherwise, search for a file or package called greeter (Topic 6.2 shows where). (3) Create an empty module object. (4) Store it in sys.modules. (5) Run the file's code inside that object, top to bottom. (6) Bind the name greeter in your file.

Step 5 is the surprising one: importing runs code. If the file has a print at the top level, it prints, once. Step 4 comes first on purpose: while the file runs, the half-built module is already in sys.modules, which is what makes circular imports (Topic 6.3) behave the way they do. If step 5 raises an error, Python removes the entry again, so the next import tries afresh.

What happens on the first importdiagram
Rendering diagram…

03The cache: a second import does nothing

Because of step 1, importing the same module again is nearly free and never reruns the file. Both names below point at the single object stored in sys.modules["greeter"]. That is also how a module acts as a shared singleton (one object that everybody uses): a settings module imported by ten files is one object, not ten.

If you edit a module while a program is running, the new code is not picked up. importlib.reload(module) forces a rerun. It is useful in a notebook or the REPL, and rarely in real programs.

main.pywhole filepython
import sys
import greeter          # runs greeter.py now
import greeter as again # does not run it again

print(sys.modules["greeter"] is greeter is again)

04from-import copies a reference

from settings import limit makes a new name limit in your file that points at the same value settings.limit pointed at at that moment. If code later does settings.limit = 99, your limit still shows the old value, because you hold your own label on the old object.

When a value can change while the program runs, import the module and read settings.limit each time. When a name is a stable function or constant, from is fine and reads well.

main.pywhole filepython
import settings
from settings import limit      # a snapshot of the value now

settings.limit = 99
print(settings.limit)   # 99: looks up the name in the module each time
print(limit)            # still the old value

05Why import * is a trap

A star import copies every public name (every name without a leading underscore, unless the module defines __all__, Topic 6.3). If the module defines something called sum, max or list, it replaces yours, with no warning.

Nothing is wrong with Python here. It does what you asked. The problem is that a reader of your file cannot tell where helper() came from, and an upgrade of the other module can add a new name that quietly breaks yours.

main.pywhole filepython
from mathy import *      # mathy.py defines its own sum()

print(sum([1, 2, 3]))    # not the built-in any more

06Two different import errors

ModuleNotFoundError means Python could not find a module of that name at all (a typo, or a package that is not installed). ImportError: cannot import name means the module exists but does not contain that name. Python 3.12 and later often adds a helpful "Did you mean".

ModuleNotFoundError is a more specific kind of ImportError (Topic 7.1 explains how errors are grouped into families), so except ImportError catches both. Library code uses this to offer optional features: try to import a fast package, and fall back to a slower built-in one.

In the snippet, try: means "attempt this", and except ImportError: means "if that failed with an import error, do this instead". Topic 7.1 teaches try fully.

main.pywhole filepython
try:
    import orjson as json_lib      # a fast third-party JSON library
except ImportError:
    import json as json_lib        # the standard library always works

print(json_lib.__name__)

07Where imports go, and how they are grouped

PEP 8 (Topic 1.11) asks for imports at the top, in three blocks: the standard library, then third-party packages, then your own project's modules, with a blank line between blocks and each block sorted alphabetically.

The grouping tells a reader at a glance what your file needs from the world. A long list from the third block means your file does too much; a long list of standard-library imports in a tiny file is usually fine.

main.pywhole filepython
import json
import sys
from pathlib import Path

import httpx
from pydantic import BaseModel

from myapp.config import load_settings
from myapp.storage import save

Try it yourself

  1. 1

    Predict the reload

    In the second example, comment out the importlib.reload(greeter) line and run it. How many times does greeter.py is running now appear? Predict, then check. Put the line back and the count changes to two.

  2. 2

    Import it two ways

    In the first example, add from math import sqrt as root and print root(81). Then print math.sqrt is root. It prints True: a from-import copies the reference to the very same function.

  3. 3

    Catch the star-import

    In the third example, change from mathy import * to from mathy import helper, run it, and watch sum stay the built-in. This is the fix: import only the names you mean to use.

Code & diagrams

Three import styles New tab

`math` and `m` print the same result because they are the same module object. Try `print(sqrt)` to see that a from-import gives you the function itself.

Sign in to run this example in your browser.

Expected output

4.0 5.0 6.0
pi is about 3.1416
same module object: True
math in sys.modules: True
type of sys.modules: dict
module math
The first import runs the file, later ones do not New tab

The example writes `greeter.py` first, so you can watch the import happen. `Path(...).write_text(...)` puts text into a file (Topic 7.6 teaches `pathlib`), and `\n` inside the text is a new line. `importlib.invalidate_caches()` tells Python to re-read the folder, which is needed when a file was created a moment ago. `importlib.reload` forces a second run.

Sign in to run this example in your browser.

Expected output

before import: False
greeter.py is running now
after first import: True
after more imports, still one run
True hello
greeter.py is running now
reload runs the file again
A star import silently replaces the built-in sum New tab

The file `mathy.py` is written first (`def` makes a function, Phase 5). It defines its own `sum`. After `from mathy import *`, the name `sum` in this file points to it. The real built-in is still reachable through the `builtins` module.

Sign in to run this example in your browser.

Expected output

before: 6
after star import: my own sum
builtin still fine: 6
What import does, as a flowchartdiagram
Rendering diagram…
A real use: a shop's bill calculator in its own module New tab

This is why modules exist. The tax rule lives in one file, `billing.py`. Every other file that needs a bill imports it, so the rate is changed in one place only. Here the file is written first so the example is self-contained.

Sign in to run this example in your browser.

Expected output

subtotal: 255.49
with GST: 301.48
rate used: 0.18

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Misspell a module name

Write import mathh instead of import math.

main.pywhole filepython
import mathh
print(mathh.sqrt(16))
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 1, in <module>
import mathh
ModuleNotFoundError: No module named 'mathh'

Break #2

Import a name that is not there

Write from math import sqroot.

main.pywhole filepython
from math import sqroot
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 1, in <module>
from math import sqroot
ImportError: cannot import name 'sqroot' from 'math' (unknown location). Did you mean: 'sqrt'?

Myth vs fact

Myth

import just copies the file's text into mine, like a paste.

Fact

It runs the file once in its own namespace and gives you the resulting module object. Its names live in the module, which is why you write math.sqrt and not just sqrt.

Myth

Importing a module twice runs it twice.

Fact

The second import finds the module in sys.modules and does nothing. Only importlib.reload runs it again.

Myth

from x import name is faster or lighter than import x.

Fact

Both load the entire module. The difference is only which names appear in your file.

Myth

Star imports are fine when the module is small.

Fact

Small modules grow. The risk is silent replacement of your names, and a reader who cannot tell where a name comes from.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Import time is real. A module with heavy top-level work (loading a model, opening a connection, importing a huge library) slows every program that imports it, including quick command-line tools and tests. Keep import-time work small: define things, do not run them. Python 3.15 adds explicit lazy imports for exactly this problem.

  • ▸

    python -X importtime main.py prints how long each import took, in microseconds, to stderr. It is the first tool to reach for when a program starts slowly, and it often shows that one import of an AI library takes most of the startup.

  • ▸

    sys.modules is a plain dictionary, and the import system is built on documented hooks (finders on sys.meta_path, loaders in importlib). Tests sometimes patch sys.modules to replace a heavy dependency, and plugin systems load modules by name at run time with importlib.import_module("pkg.name"). Two traps follow from the cache: importlib.reload re-runs the file in the same module object, so objects created earlier (classes, instances) are not replaced and isinstance checks can behave oddly; and a name imported with from m import x keeps the old object after a reload.

  • ▸

    Because modules are cached singletons, module-level state is shared by every importer. That is convenient for configuration and dangerous for anything mutable and per-request. Prefer passing objects into functions over keeping them in module globals.

Remember this

  1. 1

    A module is just a Python file. math.py, random.py and your own helpers.py are all modules. The statement import math means: find the module called math, run it if it has not been run yet, and bind the name math in my file to the finished module object. After that, math.sqrt means "the name sqrt inside that module".

  2. 2

    There are three common forms. import math gives you the module under its own name. import math as m gives it a shorter name (import numpy as np is a famous example). from math import sqrt, pi copies names out of the module into yours, so you write sqrt(16) with no math. prefix. All three load the whole module; they differ only in which names appear in your file.

  3. 3

    The first import of a module does real work: Python searches for the file, reads it, turns it into bytecode (the compact instructions Python really runs, Topic 0.8), and runs it from top to bottom. This is when its def and print lines happen. Python stores the module object in the dictionary sys.modules, keyed by name, just before it runs the file (so the entry already exists while the file runs, and is removed again if the file fails with an error). The second import of the same name finds the key and does nothing more. This is why a module's top-level print runs only once, and why two files that import config share the same config object.

  4. 4

    import * (as in from math import *) dumps every public name of a module into yours. It is discouraged because you can no longer tell where a name came from, and a name from the module can silently replace one of yours, or even a built-in such as sum. Prefer import module or an explicit from module import name.

  5. 5

    from m import name makes your own label for whatever name pointed at at that moment (Topic 1.1: a name is a label stuck on a value). If the module later points its name at a new value, your label does not follow. Use import m and write m.name when you need the live value.

  6. 6

    The two forms also fail differently. A missing module gives a ModuleNotFoundError. A missing name inside a module that does exist gives an ImportError.

  7. 7

    Imports usually go at the top of the file, grouped in three blocks separated by a blank line: standard library first, third-party packages second, your own modules third. Tools such as Ruff (Topic 11.6) sort and check this for you. A file whose imports you can read in five seconds is a file whose dependencies you understand.

Explain it without notes

01

What exactly happens when Python executes import greeter for the first time?

02

What is sys.modules, and what does it explain?

03

What is the difference between import x and from x import name?

04

Why is from module import * discouraged?

Practice

01

Import sqrt from math under the name root, then print root(144).

02

Write a program that imports json, then prints "json" in sys.modules (import sys too) and the module's name json.__name__.

03

Write a file shapes.py that sets SIDES = 4 and prints loading shapes. (Create it from your program with Path("shapes.py").write_text(...), as in the second example.) Import it twice and show that the message appears only once.

04

Import sys. Print "antigravity" in sys.modules. Then import the module colorsys (do not import antigravity) and print the same check again. Explain the two answers.

Trade-offs

  • ↔

    **import x vs from x import name.** The first keeps names qualified and always reads the live value, at the cost of typing the prefix. The second is shorter but takes a snapshot and hides the origin. Use import x for big modules and from for a few clearly named helpers.

  • ↔

    Importing at the top vs inside a function. Top-level imports make dependencies visible and fail early. An import inside a function delays a slow or optional import until it is needed, or breaks a circular import (Topic 6.3), but it hides dependencies and repeats a (cheap) cache lookup on every call.

Done when you can

  • Done when you can explain the six steps of a first import, including that the file is executed.

  • Done when you can show that a second import does not rerun a module, using sys.modules.

  • Done when you can write import x, import x as y and from x import a as b, and say which names each creates.

  • Done when you can explain why from x import * is discouraged, with the sum example.

  • Done when you can tell a ModuleNotFoundError from an ImportError: cannot import name.