Splitting a Program Into Pieces
A single file works exactly until it does not, somewhere around the point it holds three unrelated things. Split one program across two files, import one from the other, and watch the boundary hold.
A single file works exactly until it does not, somewhere around the point it holds three unrelated things. Split one program across two files, import one from the other, and watch the boundary hold.
A module, a package, and a library are not the same word
People use these three words as if they mean the same thing, and mixing them up makes documentation harder to read than it needs to be. A module is one file — shapes.py is a module. A package is a folder of modules that Python can import as a single unit, which the next section shows. A library is neither of those specifically — it is the general word for a chunk of published, reusable code, and it might be shipped as a single module, a package, or several packages together.
Module
Package
Library
requests, the package almost every Python program that talks to the internet ends up importing, is a library. So is the standard library itself — the collection of modules that ship with Python before you install anything, like json or pathlib. Calling json “a library” and calling it “a module” are both correct, at different levels of description; calling your own single shapes.py file “a package” is not, because nothing about it is a folder.
One file becomes two, on purpose
Any .py file can be imported by another. Put the shared logic in its own file, and every other file that needs it imports it by name — the file name becomes the module name, minus the .py.
def area_of_circle(radius): return 3.14159 * radius * radiusimport shapes print(shapes.area_of_circle(4))Nothing about shapes.py changed to make it importable — every .py file already is. What changed is that main.py now reaches into it by name instead of duplicating area_of_circle inside itself, which is the entire point of splitting a program up in the first place.
What import actually does the first time
The first time a module is imported anywhere in a running program, Python actually runs the entire file top to bottom, once, then keeps the result cached. Every later import shapes, anywhere else in the program, reuses that same cached result rather than running the file again.
Only when the file is run directly
A module's top level runs the moment anything imports it — the previous section already showed that. That becomes a problem the instant a file is meant to work two ways: as a script you run directly, and as a module something else imports. Code meant to run only in the first case needs a way to say so.
def fetch_forecast(city): return f"Sunny in {city}" print(fetch_forecast("London"))Import this from anywhere else in the program, and that print runs immediately, every time — reporting London's forecast into output that has nothing to do with weather, the moment the import line executes.
def fetch_forecast(city): return f"Sunny in {city}" if __name__ == "__main__": print(fetch_forecast("London"))__name__ is a variable Python sets automatically in every module. Run the file directly and Python sets it to the string "__main__". Import the same file from somewhere else and Python sets it to the module's own name instead — "weather", not "__main__" — so the condition is false, and the print never runs. Nothing magic is happening here; it is an ordinary if statement checking an ordinary variable that happens to be set differently depending on how the file started.
A package is a folder with one extra file in it
A package is just a folder of modules, with one marker file, __init__.py, that tells Python to treat the folder as a single importable unit rather than an ordinary directory.
$ geometry/ __init__.py shapes.py angles.pyFrom outside the folder, from geometry import shapes reaches into it the same way import shapes reached into a single file — the folder structure is invisible to the code that uses it.
How Python actually finds what you import
import shapes works because Python searches a specific list of locations, in order, called sys.path: the folder the running script lives in first, then any installed packages, then the standard library. That is also why a typo like import shpaes fails immediately with ModuleNotFoundError instead of finding something similar — the search is exact, not fuzzy.
Inside a package, an import can be written two ways. An absolute import spells out the full path from the top of the package, the same way outside code would; a relative import uses dots to mean “from here” instead.
# absolute — same path anyone outside the package would writefrom geometry.shapes import area_of_circle # relative — "from the module next to me in this same package"from .shapes import area_of_circleRelative imports save typing inside a large package and keep working if the whole package is ever renamed or moved, since the dot just means “my neighbour” rather than repeating the package's name. They only work inside a package, though — a plain script run directly cannot use one, because a single file run on its own has no package to be relative to.
Key takeaways
- A module is one file; a package is a folder of modules with an __init__.py; a library is the general word for published code that might be shipped as either.
- A module's file runs top to bottom exactly once per program, the first time it is imported, then the result is cached.
- if __name__ == "__main__": guards code so it only runs when the file is executed directly, not when something else imports it — __name__ is just an ordinary variable Python sets differently depending on how the file started.
- Python searches sys.path, in order, to resolve an import — the running script's own folder first, then installed packages, then the standard library.
- A relative import (from .shapes import ...) only works inside a package; a plain script run directly needs an absolute one.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.