Filter concepts by levelShowing all levels.

Python · Python Fundamentals

Modules and imports

Concepts
8
Python overview

Modules and packages

The two units of reuse — one file, or a directory of them.

Modules

corebeginner

A module is just a `.py` file. `import mathy` runs mathy.py once and binds the name `mathy` to the resulting namespace object, so every name mathy.py defined is reachable as mathy.something.

Think of it as

A module is a box that gets sealed the moment its file finishes running. import does not hand you the source code to re-read — it hands you the sealed box, labeled with the module name, holding whatever the file assigned at the top level while it ran.

python
import mathy                        # bind the module itself
import mathy as m                   # bind it under a shorter name
from mathy import area              # bind one name directly
from mathy import area as circle_area  # bind it, renamed

What we're doing: Import a module two different ways and show that both paths reach the same underlying object.

mathy.pypython
# mathy.py
PI = 3.14159

def area(radius):
    return PI * radius ** 2


# main.py
import mathy
from mathy import area as circle_area

print(mathy.area(2))
print(circle_area(2))
print(mathy.PI)
1
mathy.py has nothing marking it as importable — any .py file qualifies.
10
import mathy runs the whole file once and binds the name mathy to what it produced.
11
from mathy import area as circle_area reaches into that same run and binds one name directly, renamed.
13
mathy.area and circle_area are the exact same function object — two names, one function.
Output
12.56636
12.56636
3.14159

Why this works: import mathy executes mathy.py exactly once, top to bottom, and collects every name the file assigned at module level into one namespace object bound to mathy. from mathy import area as circle_area runs the same file (or reuses the cached run — see the import caching concept) and then reaches directly into that namespace for area, binding it locally under a new name. Both mathy.area and circle_area point at the identical function object, which is why calling either gives the identical result.

Assuming a module's names appear on their own, without importing it

Wrong

python
print(mathy.PI)  # NameError — mathy was never imported

Better

python
import mathy

print(mathy.PI)

What you see: NameError: name 'mathy' is not defined — the file exists on disk, but nothing has run it yet.

Why: A .py file sitting in a directory is not automatically part of the running program — Python only executes it, and creates the namespace object for it, in response to an explicit import statement. Being importable and being imported are different things; the file has to actually be imported before any of its names exist in the current scope.

A file becomes a namespace on import

mathy.py

PI = 3.14159, def area(r): ...

import mathy

the file runs, top to bottom, once

mathy

a namespace: mathy.PI, mathy.area

  1. mathy.py — PI = 3.14159, def area(r): ...
  2. import mathy — the file runs, top to bottom, once
  3. mathy — a namespace: mathy.PI, mathy.area

Ways to bring a module — or a name from it — into scope

Ways to bring a module — or a name from it — into scope
FormWhat it binds
import mathymathy — access everything as mathy.thing
import mathy as mm — same module object, shorter name
from mathy import areaarea directly — no mathy. prefix needed
from mathy import area as circle_areacircle_area — renamed on the way in

Together

python
import mathy
from mathy import area as circle_area

print(mathy.PI)
print(circle_area(2))

Remember: import runs a .py file once and hands back a namespace object — mathy.thing reaches into it; from mathy import thing skips the prefix.

See also: packages · imports · import resolution

Packages

corebeginner

A package is a directory that Python can import as a single name, because it contains an __init__.py file. Its submodules are reached with dotted paths — shop.pricing, shop.utils.formatting — mirroring the folder structure on disk.

Think of it as

A module is one file; a package is a folder of them wearing a name tag. The name tag is __init__.py — remove it and the folder is just a folder again, invisible to import, no matter how many .py files sit inside it.

python
import shop.pricing                       # dotted path all the way down
from shop.pricing import apply_discount   # skip straight to a name inside it
from shop.utils.formatting import as_currency

What we're doing: Import from a two-level package by dotted path, and directly by name.

main.pypython
# shop/__init__.py        (empty)
# shop/pricing.py          def apply_discount(price, pct): ...
# shop/utils/__init__.py   (empty)
# shop/utils/formatting.py def as_currency(amount): ...

from shop.pricing import apply_discount
from shop.utils.formatting import as_currency

price = apply_discount(100, 20)
print(as_currency(price))
1
Two empty __init__.py files are what make shop and shop/utils importable at all — neither needs any code inside it.
6
shop.pricing follows the folder structure exactly: the shop package, dot, the pricing module inside it.
7
shop.utils.formatting goes one level deeper — a package inside a package, then the module inside that.
Output
$80.00

Why this works: Every dot in an import path is a step down the directory tree, and every directory it steps into must have its own __init__.py or Python does not recognize it as a package to step into. from shop.utils.formatting import as_currency resolves in three steps — find shop, find utils inside it, find formatting.py inside that — succeeding at each because __init__.py marks the first two as real packages rather than plain, unimportable folders.

Leaving out __init__.py and expecting the directory to import anyway

Wrong

python
# shop/ has pricing.py inside it, but no __init__.py
from shop.pricing import apply_discount

Better

python
# add an empty shop/__init__.py, then:
from shop.pricing import apply_discount

What you see: ModuleNotFoundError: No module named 'shop' — even though the shop/ directory and pricing.py both exist on disk.

Why: A directory of .py files is not a package by itself — __init__.py is what tells Python "treat this folder as one importable unit." Without it, Python does not search inside the directory at all; it behaves as if pricing.py were not there.

A directory becomes a dotted namespace

shop/

__init__.py

pricing.py

shop/utils/

formatting.py

  • shop/ — has __init__.py — a package
    • __init__.py
    • pricing.py
  • shop/utils/ — nested package, one level down
    • formatting.py

A small package layout and how each piece is reached

A small package layout and how each piece is reached
Path on diskReached as
shop/__init__.pyimport shop
shop/pricing.pyimport shop.pricing, or shop.pricing.apply_discount
shop/utils/__init__.pyimport shop.utils
shop/utils/formatting.pyimport shop.utils.formatting

Together

python
from shop.pricing import apply_discount
from shop.utils.formatting import as_currency

print(as_currency(apply_discount(100, 20)))

Remember: A package is a directory with __init__.py; its submodules follow the folder structure exactly, one dot per level.

See also: modules · init py · imports

__init__.py

standardintermediate

__init__.py marks a directory as a package and runs once, on first import. It can be empty, or re-export names from submodules so a caller reaches them through the package name directly.

Think of it as

__init__.py is a package's front desk. An empty one just confirms the building is open for business — you still have to know which room (submodule) holds what you want. A front desk that re-exports names is one that has already fetched the common items and laid them on the counter, so a visitor never needs to know which room they came from.

python
# shop/__init__.py
from .pricing import apply_discount   # re-export a name from a submodule

__all__ = ["apply_discount"]          # what "from shop import *" actually pulls in

What we're doing: Re-export a name in __init__.py and show it becomes reachable directly through the package, without naming the submodule that actually defines it.

main.pypython
# shop/__init__.py
from .pricing import apply_discount

__all__ = ["apply_discount"]


# shop/pricing.py
def apply_discount(price, pct):
    return price * (1 - pct / 100)


# main.py
from shop import apply_discount   # no ".pricing" needed

print(apply_discount(100, 20))
2
This runs once, the first time anything imports shop — it pulls apply_discount out of pricing.py and binds it inside shop's own namespace.
4
__all__ declares what "from shop import *" would pull in — it does not affect a named import like the one below.
13
main.py imports apply_discount straight from shop, with no mention of pricing — the re-export in __init__.py is what makes that possible.
Output
80.0

Why this works: Importing shop always runs shop/__init__.py first, exactly once, and from .pricing import apply_discount inside it is a normal relative import that binds apply_discount into shop's own namespace as a side effect of that run. Once bound there, apply_discount is an attribute of the shop package itself — reachable as shop.apply_discount, or directly via from shop import apply_discount — with no need for a caller to know it was actually defined in pricing.py.

Expecting a submodule to be reachable through the package without being imported anywhere

Wrong

python
# shop/__init__.py is EMPTY
import shop

print(shop.pricing.apply_discount(100, 20))  # AttributeError

Better

python
# either import the submodule explicitly first...
import shop.pricing
print(shop.pricing.apply_discount(100, 20))

# ...or re-export it from __init__.py, as shown above

What you see: AttributeError: module 'shop' has no attribute 'pricing' — even though shop/pricing.py exists on disk.

Why: import shop only runs shop/__init__.py — it does not automatically import every .py file that happens to sit in the shop/ directory. A submodule becomes an attribute of its package only once something actually imports it, whether that's a caller importing it directly (import shop.pricing) or __init__.py importing it on the package's behalf.

What __init__.py's content changes for a caller

What __init__.py's content changes for a caller
shop/__init__.py containsHow a caller reaches apply_discount
(empty)from shop.pricing import apply_discount — must name the submodule
from .pricing import apply_discountfrom shop import apply_discount — re-exported directly

Together

python
# shop/__init__.py
from .pricing import apply_discount

__all__ = ["apply_discount"]

Remember: __init__.py marks a directory as a package and runs once on first import — re-export a submodule's name there to make it reachable without the submodule prefix.

See also: packages · modules · import resolution

Advertisement

The import system

Two ways to spell an import, where it searches, and what it caches.

Absolute and relative imports

coreintermediate

An absolute import spells the full path from the top-level package. A relative import uses leading dots instead, counted from the current module, and only works inside a package.

Think of it as

Absolute is a street address — the same no matter where you are standing when you say it. Relative is "two doors down from here" — it only means something if the listener knows where "here" is, which is exactly why a relative import breaks the moment a file is run directly instead of imported as part of its package.

python
from shop.pricing import apply_discount    # absolute — from the top-level package
from .pricing import apply_discount        # relative — same directory as this file
from ..other_package import thing          # relative — one level UP from this file

What we're doing: Import the same function two ways from inside shop/checkout.py — absolute, then relative — and run both through shop's __main__.py.

shop/checkout.pypython
# shop/checkout_absolute.py
from shop.pricing import apply_discount

def total_abs(price, pct):
    return apply_discount(price, pct)


# shop/checkout_relative.py
from .pricing import apply_discount

def total_rel(price, pct):
    return apply_discount(price, pct)


# shop/__main__.py
from .checkout_absolute import total_abs
from .checkout_relative import total_rel

print(total_abs(100, 20))
print(total_rel(100, 20))
2
The absolute form spells shop.pricing in full — this works the same regardless of which file writes it.
9
The relative form, one dot, means "pricing.py next to this file" — checkout_relative.py and pricing.py are siblings in the same package.
20
Both total_abs and total_rel give the identical result — the two import styles reach the same function, just spelled differently.
Output
80.0
80.0

Why this works: Both import styles resolve to the exact same module object, because Python's import system ultimately turns a relative import into an absolute one internally, using the importing module's own __package__ to fill in what the dots refer to. Absolute imports never depend on where the importing file lives; relative imports only work when Python already knows what package that file belongs to — which requires the file to have been imported as part of a package, not run directly.

Running a file with relative imports directly instead of as part of its package

Wrong

python
# python shop/checkout.py    <- run directly, from the command line
from .pricing import apply_discount

Better

python
# python -m shop.checkout    <- run as a module, inside its package
from .pricing import apply_discount

What you see: ImportError: attempted relative import with no known parent package.

Why: Running python shop/checkout.py executes the file with __name__ set to "__main__" and no known package — there is no "here" for a relative import's dots to be relative TO. python -m shop.checkout instead imports checkout as the shop package's checkout submodule first, which gives it a real package context the dots can resolve against.

Same target, two ways to name it

shop.pricing

absolute — full path, from anywhere

same file

shop/pricing.py

.pricing

relative — one dot, from checkout.py's position

  1. shop.pricing — absolute — full path, from anywhere
  2. same file — shop/pricing.py
  3. .pricing — relative — one dot, from checkout.py's position

Absolute vs. relative, from inside shop/checkout.py

Absolute vs. relative, from inside shop/checkout.py
FormMeaning
from shop.pricing import apply_discountabsolute — full path from the top package
from .pricing import apply_discountrelative — pricing.py, same directory
from .utils.formatting import as_currencyrelative — one level down, same package
from . import pricingrelative — import the sibling module itself

Together

python
# shop/checkout.py
from .pricing import apply_discount
from .utils.formatting import as_currency

def total(price, pct):
    return as_currency(apply_discount(price, pct))

Remember: Absolute spells the full path and works anywhere; relative uses dots from the current file and only works inside a package.

See also: packages · circular imports · name and main

Import resolution and caching

standardintermediate

import name searches sys.path in order and stops at the first match. Once found, the module is cached in sys.modules, so a second import reuses the same object instead of running the file again.

Think of it as

sys.path is a list of folders checked in order, like a search party checking rooms one at a time and stopping the instant someone is found — the first matching name wins, even if a "better" match sits in a folder checked later. sys.modules is the sign-in sheet: once a module has signed in, every later import just reads the sheet instead of sending the search party out again.

python
import sys
sys.path[0]          # the running script's own directory — checked first
sys.modules           # dict of {module name: module object}, the import cache

What we're doing: Import the same module twice and show the file only runs once, by watching a top-level print statement.

main.pypython
# counter.py
print("counter.py executing")
count = 0


# main.py
import counter
import counter

counter.count = 5
import counter
print(counter.count)
7
The first import runs counter.py top to bottom — this is the only time "counter.py executing" prints.
8
The second import finds counter already in sys.modules and reuses it — the file does not run again, so nothing prints a second time.
11
A third import, after mutating counter.count, still reuses the cached module — count stays 5, not reset back to 0.
Output
counter.py executing
5

Why this works: The first import counter finds no entry for 'counter' in sys.modules, so Python searches sys.path, locates counter.py, runs it top to bottom, and stores the resulting module object in sys.modules['counter'] before binding the local name counter to it. Every import statement after that — anywhere in the program, in any file — checks sys.modules first, finds the cached entry, and reuses it directly; the file's top-level code, including that print statement, never runs a second time in the same process.

Expecting a second import to reset module-level state

Wrong

python
import counter
counter.count = 5

import counter          # hoping this "reloads" and resets count
print(counter.count)    # still 5, not 0

Better

python
import importlib
import counter
counter.count = 5

importlib.reload(counter)  # explicitly re-runs counter.py
print(counter.count)       # 0 — genuinely reset

What you see: Mutated module state survives a repeated import statement instead of resetting, which reads as a bug if the caching behavior isn't expected.

Why: import never means "run this file again" after the first time — it means "give me whatever is in sys.modules for this name," and a plain assignment like counter.count = 5 changes that cached object in place. importlib.reload() is the explicit escape hatch that actually re-executes the file and replaces the cached state; a bare import statement is never enough on its own.

sys.path's search order, top to bottom

sys.path's search order, top to bottom
PositionWhat it holds
sys.path[0]the running script's own directory (or '' for -c/interactive)
PYTHONPATH entriesdirectories from that environment variable, if set
standard libraryjson, os, sys — Python's own bundled modules
site-packagesanything installed with pip

Together

python
import sys
print(sys.path[0])          # this script's own folder, checked first
print('json' in sys.modules)  # True once json has been imported anywhere

Remember: sys.path is searched in order (script directory first); sys.modules caches every result, so a module only ever runs once per process.

See also: modules · sys path · circular imports

sys.path

standardintermediate

sys.path is a plain list of directory strings that import searches in order, stopping at the first match. It is an ordinary list, so code can read it or append to it at runtime.

Think of it as

sys.path is a paper list taped to the door of Python's search party — an ordinary, mutable list of strings, not a fixed system setting. Reading it explains every import result; appending to it, however unusual, is a real way to make a new directory searchable for the rest of the process.

python
import sys
sys.path                       # the current search list, top to bottom
sys.path.append('/some/dir')   # search there too, checked LAST
sys.path.insert(0, '/some/dir')  # search there FIRST, ahead of everything else

What we're doing: Confirm sys.path's first entry is this script's own directory, then extend the list at runtime and import something from the new directory.

path_demo.pypython
import os
import sys

print(sys.path[0] == os.path.dirname(os.path.abspath(__file__)))

sys.path.append("extra/modules")
import helper   # found because extra/modules is now on the list

print(helper.hi())
4
sys.path[0] is always this running script's own directory — checked first, before PYTHONPATH or the standard library.
6
append adds the new directory at the END of the list — it is checked only after everywhere else has already been searched and missed.
7
helper is only importable BECAUSE the append ran first — reversing the two lines would raise ModuleNotFoundError.
Output
True
hi

Why this works: sys.path is populated once, at interpreter startup, from a fixed recipe — the running script's directory, PYTHONPATH, then the standard library's own installation paths — but nothing marks it read-only afterward: it is an ordinary Python list sitting in the sys module, so appending or inserting into it takes effect immediately, for every import statement that runs afterward in that same process.

Appending to sys.path AFTER the import that needed it

Wrong

python
import helper   # ModuleNotFoundError — /extra/modules isn't searched yet
import sys
sys.path.append("/extra/modules")

Better

python
import sys
sys.path.append("/extra/modules")
import helper   # now findable

What you see: ModuleNotFoundError: No module named 'helper'.

Why: import only ever searches whatever sys.path contains AT THAT MOMENT — it does not re-check the list later if something is appended afterward. The append has to run, and take effect, strictly before the import statement that depends on it.

Common sys.path operations

Common sys.path operations
CallEffect
sys.paththe current list of searched directories, in order
sys.path[0]the running script's own directory
sys.path.append(path)adds path at the END — checked last
sys.path.insert(0, path)adds path at the START — checked first, can shadow stdlib

Together

python
import sys
sys.path.append("/extra/modules")
import some_module_that_lives_there

Remember: sys.path is a mutable list import searches in order — the script's directory comes first, and code can append to it at runtime.

See also: import resolution · modules · packages

Advertisement

Module identity and cycles

How a module knows how it was reached, and what happens when two modules need each other.

__name__ and the __main__ guard

corebeginner

Every module has a __name__ attribute: "__main__" if run directly, or the module's own name if imported. if __name__ == "__main__": guards code so it only runs on direct execution.

Think of it as

__name__ is a badge every module wears that says how it walked in the door. Walk in directly and the badge reads "__main__"; get imported by someone else and the badge reads your own module name instead. The guard is just a door that only opens for the "__main__" badge.

python
if __name__ == "__main__":
    main()   # only runs when this file is executed directly, never on import

What we're doing: Run a module directly, then import the same module, and show the guard changes what actually executes.

greet.pypython
# greet.py
def hello():
    print("hello from greet")

if __name__ == "__main__":
    hello()


# --- run directly: python greet.py ---
# prints: hello from greet

# --- imported: import greet, from another file ---
# prints nothing — hello() is never called
2
The function definition itself always runs, on import or direct execution alike — defining hello is not guarded.
5
This line only evaluates True when greet.py is the file actually run — __name__ is "__main__" only then.
6
hello() is called only inside that guard, so importing greet.py defines hello but never calls it automatically.
Output
hello from greet

Why this works: Python sets a module's __name__ before running a single line of its code, based entirely on how the module was reached: "__main__" for the file passed directly to the interpreter, the module's own dotted name for anything reached via import. The guard is nothing more than a plain if statement testing that value — it has no special syntax or interpreter support beyond being a string comparison, which is also why it can be spelled wrong (a typo in "__main__") without any error, just silently never triggering.

Leaving top-level script logic unguarded in a file meant to also be imported

Wrong

python
# report.py
def build_report():
    return "report contents"

print(build_report())   # runs on EVERY import, not just direct execution

Better

python
# report.py
def build_report():
    return "report contents"

if __name__ == "__main__":
    print(build_report())

What you see: Importing report.py from anywhere else in the program unexpectedly prints report output as a side effect of the import itself.

Why: Every top-level statement in a module runs when that module is imported, exactly as if it had been run directly — import does not skip "the script part." Wrapping anything that should only happen on direct execution in the __main__ guard is what keeps import report.py side-effect-free, safe to do purely to reuse build_report.

Same file, two ways in, two badges

python greet.py

run directly

__name__

"__main__" here, "greet" if imported

if __name__ == "__main__":

only opens for the direct-run badge

  1. python greet.py — run directly
  2. __name__ — "__main__" here, "greet" if imported
  3. if __name__ == "__main__": — only opens for the direct-run badge

__name__'s value depending on how a file is reached

__name__'s value depending on how a file is reached
How greet.py is rungreet.py's __name__
python greet.py'__main__'
import greet'greet'
python -m shop (running shop/__main__.py)'__main__'

Together

python
# greet.py
def hello():
    print("hello from greet")

if __name__ == "__main__":
    hello()

Remember: __name__ is "__main__" only when a file is run directly; if __name__ == "__main__": guards code so it never fires on import.

See also: modules · imports · circular imports

Circular imports

standardadvanced

A circular import is when module A imports B while B imports A back. Python leaves a partially finished module in sys.modules mid-cycle — from x import name can fail there, while import x plus a later x.name usually survives.

Think of it as

Two people each waiting for the other to answer the phone first, except Python answers anyway with whatever it has written down SO FAR. from x import name demands the name exist on the spot, mid-call — it either has already been written down or it hasn't. import x plus x.name later is a callback: check the notepad again once both calls are actually done.

python
import a          # module-style import — survives most cycles
a.func_a()         # attribute looked up later, not at import time

from a import func_a   # name-style import — fails if func_a isn't defined yet

What we're doing: Show a circular import surviving with the module-style form, then breaking with the name-style form.

a.pypython
# a.py
import b

def func_a():
    print("a")


# b.py
import a

def func_b():
    a.func_a()
    print("b")


# main.py
import a
a.func_a()
2
a.py imports b.py before defining func_a — at this point, a is only partially built.
9
b.py imports a right back — Python finds a already IN PROGRESS in sys.modules and hands back that partial module instead of re-running it.
12
a.func_a() inside func_b is only looked up when func_b is actually CALLED, by which point a has finished loading — so the attribute exists by then.
Output
a

Why this works: When a.py's import b line runs, Python starts loading b.py before a.py has finished — b.py's own import a then finds 'a' already present in sys.modules (registered the moment a.py started, even though it is not done yet) and reuses that partial module rather than looping forever. Because b.py only writes a.func_a() inside func_b's body, that attribute lookup does not happen until func_b is actually called — by which point a.py has long since finished running and func_a genuinely exists on it.

Importing a specific name, by value, across a circular dependency

Wrong

python
# a.py
from b import func_b   # NAME-style — needs func_b to already exist

def func_a():
    print("a")


# b.py
from a import func_a   # NAME-style, the other direction — the cycle

Better

python
# a.py
import b   # MODULE-style — b need not be finished yet, just registered

def func_a():
    print("a")


# b.py
import a

def func_b():
    a.func_a()   # looked up later, once a has actually finished

What you see: ImportError: cannot import name 'func_a' from 'a' (consider renaming '.../a.py' if it has the same name as a library you intended to import) — Python 3.14's phrasing for the same underlying problem, a partially initialized module.

Why: from a import func_a demands that func_a already be a bound attribute on a's module object at the exact moment that line executes — but a is still in the middle of running (it is the one that triggered this whole import chain) and has not reached the def func_a line yet. import a, by contrast, only needs a's name registered in sys.modules, which happens before a single line of a.py runs — the actual attribute lookup (a.func_a) is deferred to wherever it is written, which can be safely after the cycle has resolved.

Two ways to import across a cycle, and what each does at import time

Two ways to import across a cycle, and what each does at import time
FormBehavior in a cycle
from b import func_b (at module level)fails — func_b may not exist yet on the partial module
import b, then b.func_b() (inside a function)works — the lookup happens later, after both modules finish
import inside the function body, not at module topworks — defers the whole import past the cycle

Together

python
# b.py
import a

def func_b():
    a.func_a()   # looked up when func_b actually RUNS, not at import time
    print("b")

Remember: A circular import survives if the cross-reference is looked up later (import x, then x.name) — it fails immediately with from x import name.

See also: imports · modules · import resolution

Advertisement