The first time a tutorial said pip install requests, I typed it and got magic. The first hundred times, spread over six months, I got a mess: eighty-odd packages in one shared pile, no memory of which project wanted what, and one afternoon where an upgrade for a new script quietly broke an older one I hadn't touched in months. Here's how I use pip now.
pip install requests # fetch and install
pip list # everything installed right now
pip show requests # one package's details
pip show is the one I underused for years. It tells you the version, where the files live, and — the part worth the price of admission — who depends on it:
Name: urllib3
Version: 2.2.6
Requires:
Required-by: requests, botocore
That last line answers the question every pip user asks eventually, usually around midnight: why is this even installed? Because something you use imports it. Uninstall it, and requests stops working. urllib3 isn't clutter — it's load-bearing.
Every pip install without a second thought lands in the same global site-packages. For the first week that's invisible; by month six, pip list is three screens long and half of it is packages you can't explain:
Package Version
----------------- -------
beautifulsoup4 4.12.3
botocore 1.34.51
certifi 2024.2.2
requests 2.32.3
urllib3 2.2.6
... ...
No single line is wrong. The problem is arithmetic: one pile of packages, six projects, and each project quietly hoping nothing upgrades a dependency out from under it. That's the failure mode I hit — fix project A's version, and project B's script stops importing.
The fix is to stop sharing. A virtual environment is a private copy of Python for one project: its own packages, its own versions, its own pip list. You create one with python -m venv .venv, switch it on with its activate script, and from then on every pip install touches only that project's pile. Delete the .venv folder and it's gone — your system Python never noticed. It costs one command per project and retires the shared-pile arithmetic forever. I make one for every project now, including throwaway scripts. Throwaway scripts, it turns out, are the ones that come back.
pip uninstall requests
pip names exactly what it's removing and asks for confirmation. When pip list shows a package I don't remember installing, pip show and its Required-by line usually tell the story — it came in as a dependency. If Required-by is empty and I still don't recognize the name, out it goes; if something misses it, reinstalling costs ten seconds.
One exception from experience: don't "clean up" the Python your operating system uses. On Linux, system tools often sit on packages in the system Python, and I once broke a distro tool by tidying a package that looked unused. In your own project environment, an empty Required-by means safe to remove. In the system Python, leave it alone.
pip itself is simple — install, list, show, uninstall. Everything that goes wrong with it is really an architecture problem: too many projects sharing one pile. Split the pile, and pip goes back to being magic.