Backend

Python Virtual Environments 101

Why Python projects need their own environment, how to create one with venv, pipenv or uv, and the habits that keep dependencies reproducible.

Praweg KoiralaPraweg Koirala
Published on- October 14, 2026
4
Python Virtual Environments 101

Python Virtual Environments 101

Sooner or later everyone hits the same wall with Python. One project needs a library at version 2.x, another one breaks on anything newer than 1.x, and both of them are installed into the same global site-packages. Whatever you upgrade, something else stops working. A virtual environment is the fix, and it is a two-command habit once you have the pattern.

What a virtual environment actually is

A virtual environment is a directory that holds its own Python interpreter link and its own site-packages. When it is active,

pip install
writes into that directory instead of the system Python, and
import
resolves packages from there first. Nothing outside the directory changes.

That gives you three things that matter:

  • Dependencies are isolated per project, so installing something for one app cannot break another.
  • Different projects can pin different versions of the same package. A project stuck on Django 4.2 LTS and a new one on 5.2 can live side by side on the same machine.
  • Setups become reproducible. Whoever clones the repo creates their own environment from the same list of pinned versions and gets the same result you have.

Creating one with venv

venv
ships with Python, so there is nothing to install. From the project directory:

cd my-project
python3 -m venv .venv
source .venv/bin/activate        # macOS / Linux
.venv\Scripts\activate           # Windows (cmd)

The prompt picks up the environment name once it is active. From there, install what the project needs and freeze it:

python -m pip install -r requirements.txt
python -m pip freeze > requirements.txt

Two details that trip people up. First,

python -m pip
is safer than a bare
pip
, because it guarantees you are installing into the interpreter you activated. Second, from Python 3.12 onwards a new environment does not include
setuptools
by default, so a package with a legacy build step may need
python -m pip install setuptools
first.

When you are done,

deactivate
returns you to the system Python. Add
.venv/
to
.gitignore
and commit
requirements.txt
instead; environments are local, version pins are shared.

Pipenv

pipenv
combines the environment and the dependency file into one tool. In a project directory:

pipenv install

That creates the virtual environment and a

Pipfile
if neither exists. The
Pipfile
has two sections:
[packages]
for runtime dependencies and
[dev]
for things like test runners and linters. Install a package and it gets added there:

pipenv install requests==2.32.3
pipenv install --dev pytest

pipenv install
after that reads the
Pipfile
, installs the missing pieces and writes
Pipfile.lock
, which pins every transitive dependency so two machines end up identical.
pipenv shell
activates the environment, and
exit
leaves it. (
pipenv exit
is not a command, which is the kind of thing you only find out when it hangs the first time.)

Pipenv still works and plenty of projects use it. It is no longer the default recommendation from the packaging side, which has settled back on plain

venv
plus a locking tool. If you are starting a new project today,
venv
plus
uv
or pip-tools is the lighter path.

The fast option: uv

uv
is a resolver and installer written in Rust that most Python developers I know have switched to over the last year, largely because it is an order of magnitude faster and handles the environment for you:

uv venv                       # create .venv
source .venv/bin/activate
uv pip install -r requirements.txt

# or manage a project without activating anything:
uv init
uv add requests
uv run python main.py

uv run
is the part I like: it uses the project environment without you remembering to activate it first, so a scheduled script or a cron job behaves the same as your terminal.

The rules I keep

  • One environment per project. Never install project packages into the system Python if you can avoid it.
  • Commit the pinned list (
    requirements.txt
    ,
    Pipfile.lock
    ,
    uv.lock
    ) and ignore the environment directory.
  • Pin versions for anything that goes to production. Floating versions are how a build that worked on Friday fails on Monday.
  • When an environment gets strange, delete
    .venv
    and recreate it. It takes seconds and saves an afternoon of debugging the environment itself.

If you want to see the payoff, the RAG agent walkthrough and the Titanic machine learning tutorial both pull in a handful of data and ML packages that would be a mess to install globally. Same story with running ComfyUI on a rented GPU, where the app expects its own Python with its own dependency set.

Comments

Leave a Comment

Sign in to leave a comment

Your comment will appear after approval by the blog moderator.

No comments yet.