You find a package that does exactly what you need, run pip install, watch it report success, then run your program and Python says it has never heard of it:
ModuleNotFoundError: No module named 'tabulate'
Nothing is broken. You almost certainly installed the package into a different Python than the one running your code. This part is about controlling exactly where packages go, using two tools every Python developer touches daily: pip, which installs packages, and venv, which gives each project its own clean space to keep them.
python -m venv .venv, activate it, then pip install the packages that project needs. Everything installs inside that folder, isolated from every other project. Save the list with pip freeze > requirements.txt so anyone can rebuild it.This part assumes you can run Python from a terminal, which was Part 2. You need Python 3 and about twenty five minutes. pip and venv both ship with Python, so there is nothing to install before you begin.
What pip is, and where packages come from
pip is Python’s package installer. It downloads code other people have written and published, so you do not have to build everything yourself. That library of published packages is called PyPI, the Python Package Index. Installing one is a single command, and the safest way to write it is through the interpreter itself so there is no doubt which Python you are feeding:
py -m pip install tabulate
python3 -m pip install tabulate
Writing python -m pip instead of a bare pip matters. A bare pip can point at a different Python than the one you run your scripts with, which is the exact cause of that ModuleNotFoundError above. Tying pip to a specific interpreter removes the guesswork.
Why every project needs its own environment
Without a virtual environment, every package you install piles into one shared Python. Two projects that need different versions of the same library then collide, and one of them breaks. On top of that, current versions of macOS and many Linux systems now refuse a global install outright to protect the Python the operating system depends on:
error: externally-managed-environment
That red block frightens people, but it is a signpost, not a wall. The system is telling you not to install into its Python, and the intended answer is the same thing you should do anyway: create a virtual environment and install there. A virtual environment is a private copy of Python and its packages that lives in a folder inside your project.
Making and using a virtual environment
From your project folder, create one with the built-in venv. The name .venv is a common convention, and the leading dot keeps it tidy:
py -m venv .venv
python3 -m venv .venv
Creating it is not enough; you have to activate it, and the command differs by system. Once active, your prompt shows (.venv) and every pip install lands inside that folder alone.
| Step | Windows (PowerShell) | macOS / Linux |
|---|---|---|
| Create | py -m venv .venv | python3 -m venv .venv |
| Activate | .venvScriptsActivate.ps1 | source .venv/bin/activate |
| Install | pip install tabulate | pip install tabulate |
| Leave | deactivate | deactivate |
Table 1. Creating and using a virtual environment, side by side per system.
After activating, you can drop the python -m habit if you like, because inside an active environment a bare pip and python both point at that environment. When you are done working, deactivate returns you to the normal shell.
Figure 1. Each project has a private .venv, so their packages never clash.
A nicer table for the tracker
Back in Part 10 you aligned the receipt by hand with format codes. That was worth doing once, to see how it works. Now that you can install packages, a small library called tabulate does the alignment for you and handles the column widths automatically. Inside your activated environment, install it, then feed it your expenses:
from tabulate import tabulate
expenses = [
{'category': 'food', 'amount': 120.5},
{'category': 'travel', 'amount': 40},
]
rows = [[e['category'], float(e['amount'])]
for e in expenses]
print(tabulate(rows,
headers=['Category', 'Amount'],
floatfmt='.2f'))Category Amount ---------- -------- food 120.50 travel 40.00
The library right aligns the numbers, pads the columns to fit, and draws the divider, all from one call. This is the point of packages. Someone solved the tidy table problem well, published it, and now your project uses it in three lines instead of reinventing it. Your tracker only depends on tabulate because the project’s environment does, not the whole machine.
Recording what you installed
A teammate who clones your project needs the same packages. You do not send them the .venv folder; you send a list and let them rebuild it. pip freeze writes that list, with exact versions, into a file everyone agrees to call requirements.txt:
pip freeze > requirements.txt
tabulate==0.9.0
Anyone with that file recreates your setup in one command, pip install -r requirements.txt, inside their own fresh environment. Two firm rules go with this. Commit requirements.txt to your project so the dependency list travels with the code. Never commit the .venv folder itself; it is large, machine specific, and rebuilt in seconds, so it belongs in your .gitignore. I will disagree openly with any advice that tells a beginner to sudo pip install a package system wide. It saves ten seconds today and causes version conflicts you cannot untangle later.
pip install succeeds but import still fails, the package went into a Python your script is not using. The usual cause is installing before activating the environment, or using a bare pip that points elsewhere. Activate the environment first, then install, and use python -m pip when in doubt about which interpreter you are feeding.requirements.txt with pip freeze so the environment is reproducible. That pairing tells an interviewer you have worked on a shared codebase.cowsay. Then run the two lines below and confirm you see a talking cow. Expected output (roughly):
____
| hi |
====
^__^Show solution
python3 -m venv .venv source .venv/bin/activate pip install cowsay
import cowsay
cowsay.cow('hi')On Windows, create with py -m venv .venv and activate with .venvScriptsActivate.ps1. If the import works, your environment is set up correctly.
pip and venv questions that come up
Does pip come with Python, or do I install it separately? It comes bundled with every modern Python 3. If a system somehow lacks it, python -m ensurepip restores it, but you rarely need that.
Should I commit the .venv folder to git? No. Commit requirements.txt and add .venv to .gitignore. The folder is large and specific to your machine, and it rebuilds instantly from the requirements file.
Is it pip or pip3? Both exist on macOS and Linux and it causes confusion. Use python3 -m pip outside an environment, and a plain pip once an environment is active, and you avoid the ambiguity entirely.
What is that externally-managed-environment error? Your operating system is refusing to let pip change its own Python. It is not a bug. Create a virtual environment and install there, which is what you wanted to do regardless.
Do I need conda or Anaconda? Not for this series. venv and pip cover everything here. conda is useful for heavy data science stacks, but it is extra weight you do not need yet.
Everyday pip commands
Install is the command you reach for first, but a handful of others come up almost as often once a project has real dependencies. They let you see what is installed, inspect a single package, move to a newer version, or remove one you no longer need. All of them act on the environment that is currently active, which is exactly what you want.
pip list pip show tabulate pip install --upgrade tabulate pip uninstall tabulate
| Command | What it does |
|---|---|
pip list | Show every package in this environment |
pip show NAME | Version and details of one package |
pip install --upgrade NAME | Move a package to its newest version |
pip uninstall NAME | Remove a package from this environment |
Table 2. The pip commands you will run after the first install.
Of these, pip list earns its keep as a sanity check. It is the quickest way to confirm you are in the right place: a fresh .venv shows just pip and a couple of built-ins, while a cluttered global Python shows a long list of things you do not remember installing. If you are ever unsure which environment you are in, run it and read the length of the output.
Pinning versions so builds do not drift
The line tabulate==0.9.0 in your requirements file is a pinned version, meaning that exact release and no other. This is deliberate. If you wrote just tabulate with no version, a rebuild months later could pull a newer release whose behaviour differs, and your program might break for reasons that have nothing to do with anything you changed. Pinning removes that surprise.
Those three numbers, as in 0.9.0, follow a common convention: major, minor, and patch. A patch bump fixes bugs, a minor bump adds features without breaking old ones, and a major bump can change behaviour you relied on. Knowing which part moved helps you judge how risky an upgrade is before you run it.
Pinning trades a little manual upgrading for repeatable builds, and on any project with more than one person that trade is worth making. When you do want newer versions, you upgrade on purpose with pip install --upgrade, check that nothing broke, then run pip freeze again to record the new pins. Controlled upgrades beat surprise ones every single time.
requirements.txt so anyone can rebuild them. Your tracker uses a real third party library for its table. In Part 15 you reshape the tracker’s data into a class, your first careful step into object oriented Python.


DrJha