Modules and Imports
Modules and Imports
A module is a .py file. import runs that file once and binds a name to the module object. The boring default is import prices (or from prices import cents when one name is the whole API). Keep a folder of files, not a pile of copy-paste.
Mental model
import prices looks for prices.py (or a package named prices) on sys.path. The first time, Python executes the file, stores the module in sys.modules, and binds the name prices in your module. The second import reuses sys.modules. It does not run the file again.
from prices import cents still loads the whole module. It then binds cents in your namespace. The name prices is not bound unless you also imported it.
import prices as p binds a shorter name. Use as when two modules share a last component, or when the real name is awkward.
importlib.import_module("prices") does the same lookup from a string. That is how you load a plugin name from config. You still need a real module on the path.
Worked examples
Put every file in this chapter in the same folder. Run the commands from that folder.
Case 1: import a sibling file
Save as prices.py:
# prices.py
MENU = {"latte": 400, "tea": 250, "bun": 150}
def cents(name):
if name not in MENU:
raise KeyError(f"unknown item {name!r}")
return MENU[name]Save as order.py. This is the program you run.
# order.py
import prices
def total(names):
return sum(prices.cents(n) for n in names)
def main():
print(prices.cents("latte"))
print(total(["latte", "tea"]))
if __name__ == "__main__":
main()Run:
uv run python order.pyOutput:
400
650
prices.py has no main guard. It is a library file. order.py is the program.
Case 2: from import
Save as from_order.py. Only cents is bound here. MENU is still on the module, but this file cannot see the name prices.
# from_order.py
from prices import cents
def main():
print(cents("bun"))
print(cents.__module__)
if __name__ == "__main__":
main()Run:
uv run python from_order.pyOutput:
150
prices
cents.__module__ still says prices. You imported the function, not a copy of its code.
Case 3: import ... as
Save as alias_order.py. The alias is local to this file.
# alias_order.py
import prices as menu
def main():
print(menu.cents("tea"))
print(menu.MENU["tea"])
if __name__ == "__main__":
main()Run:
uv run python alias_order.pyOutput:
250
250
Case 4: importlib from a string
Save as load_prices.py. The module name comes from a variable. That is the only reason to use importlib on day one.
# load_prices.py
import importlib
def main():
name = "prices"
mod = importlib.import_module(name)
print(mod.cents("latte"))
print(mod.__name__)
if __name__ == "__main__":
main()Run:
uv run python load_prices.pyOutput:
400
prices
If name is wrong, you get ModuleNotFoundError. Do not catch that and keep going unless you are probing optional plugins on purpose.
The trap
from prices import cents does not bind prices. Reaching for prices.MENU looks natural and is a NameError.
Save as missing_module_name.py:
# missing_module_name.py
from prices import cents
def main():
print(cents("latte"))
try:
print(prices.MENU)
except NameError as e:
print(type(e).__name__ + ":", e)
if __name__ == "__main__":
main()Run:
uv run python missing_module_name.pyOutput:
400
NameError: name 'prices' is not defined
The fix is import prices (then prices.cents and prices.MENU), or from prices import cents, MENU. Do not add from prices import *. Star imports dump unknown names into your file and make collisions invisible.
The boring rule
- One idea per module. A module name is a file name without
.py. - Prefer
import priceswhen you will use several names. Preferfrom prices import centswhen one function is the API. - Use
asfor a clash or a long name, not for decoration. importlib.import_moduleis for a name you did not know when you wrote the file.- Never
from module import *in application code. - Library modules stay quiet at import time. Work happens in functions. The
mainguard lives in the program file.
Try this
- Add
cookietoMENUat 200 cents. Runorder.pyagain and include"cookie"intotal. - In
from_order.py, also importMENUand printsorted(MENU). - In
load_prices.py, setname = "no_such_desk"and catchModuleNotFoundError. Print the exception type. - Split
totalinto its own filetotals.pythatimport prices. Haveorder.pyimport totalsand printtotals.total(["tea", "tea"]).