Virtual Environments in Python: venv, pip and Poetry

Why every Python project needs its own environment, and how to manage one: creating and activating a venv, installing and pinning packages with pip, reproducing an environment exactly from a requirements file, the difference between direct and locked dependencies, and when a tool such as Poetry or uv is worth adopting.

GloryTecks Team

Training & Placement Faculty

6 May 2026 6 min read

Last updated: 24 September 2026

Share:

Install packages with pip install into your main Python, and sooner or later two projects will need different versions of the same library. Upgrading it for one breaks the other, and neither project can say exactly which versions it needs. A virtual environment solves both problems: it is a folder with its own Python executable and its own installed packages, created per project and thrown away when no longer needed (Python docs: venv).

Creating and activating an environment

text
python -m venv .venv                  # create it inside the project folder

source .venv/bin/activate             # Linux and macOS
.venv\Scripts\activate                # Windows (Command Prompt or PowerShell)

deactivate                            # leave it

Activation only adjusts your shell so that python and pip refer to the environment's copies. You can also skip activation and call the environment's Python directly, as in .venv/bin/python or .venv\Scripts\python.exe, which is exactly what scripts and scheduled jobs should do. Name the folder .venv inside the project, and add it to .gitignore: environments contain absolute paths and compiled files, so they are recreated rather than copied or committed.

Proving you are inside one

Python itself can tell you: inside an environment, sys.prefix points at the environment while sys.base_prefix still points at the base installation.

text
$ .venv/Scripts/python -c "import sys; print(sys.prefix != sys.base_prefix)"
True
$ python -c "import sys; print(sys.prefix != sys.base_prefix)"
False

When a "module not found" error appears even though you installed the package, this check usually shows that the program started under a different Python from the one you installed into. Using python -m pip install ... rather than a bare pip guarantees that pip belongs to the Python you are calling (Python Packaging User Guide: installing packages).

Installing and pinning with pip

Install what your project uses directly, then record the complete set of installed versions:

text
$ python -m pip install "requests==2.32.3"
$ python -m pip freeze
certifi==2026.7.22
charset-normalizer==3.5.1
idna==3.20
requests==2.32.3
urllib3==2.8.0
$ python -m pip freeze > requirements.txt

One direct dependency brought four more with it. pip freeze lists all of them with exact versions, and saving that list to requirements.txt makes the environment reproducible (pip user guide). On another machine, or in a fresh environment, python -m pip install -r requirements.txt recreates it exactly; in the run behind this article, a second environment built from the file had requests 2.32.3, as recorded.

Direct dependencies and locked dependencies

There are two different lists, and mixing them up causes most dependency pain:

Direct dependenciesLocked dependencies
WhatThe packages your code importsEvery package installed, including dependencies of dependencies
VersionsRanges you support, such as requests>=2.31Exact versions, such as requests==2.32.3
Wherepyproject.toml dependencies, or a short requirements.inrequirements.txt from pip freeze, or a lock file
Changes whenYou decide to use or drop a libraryYou upgrade deliberately and re-lock

Applications should install from the locked list, so production runs exactly the versions the test suite ran against. Libraries published for others should declare ranges, so they can coexist with other packages. Editing a frozen requirements.txt by hand quickly becomes confusing, because you can no longer tell which lines you chose and which were pulled in; that is the gap tools such as Poetry and uv fill.

Poetry: dependencies and locking in one tool

Poetry manages the environment, the declared dependencies in pyproject.toml, and a lock file with exact versions (Poetry: basic usage):

text
poetry new invoicer          # or: poetry init, in an existing project
poetry add requests          # adds to pyproject.toml, resolves, updates poetry.lock
poetry install               # creates the environment and installs exactly what is locked
poetry run pytest            # runs a command inside the environment

Commit both pyproject.toml and poetry.lock. Anyone who runs poetry install gets identical versions, and upgrading is an explicit step (poetry update) that you can review. uv is a newer, very fast tool from the same space, offering a similar project workflow alongside pip-compatible commands. Either is a reasonable choice for applications; for small scripts and learning, venv plus pip is enough.

A routine that avoids most problems

  1. One environment per project, in .venv, never committed.
  2. Install with python -m pip, from inside the environment.
  3. Keep direct dependencies in pyproject.toml (see how to structure a Python project) and exact versions in a lock or requirements file.
  4. Recreate rather than repair: when an environment is in a strange state, delete .venv and reinstall from the lock file. It takes a minute and removes every doubt.
  5. Upgrade on purpose, run the tests, then commit the new lock file.

Keeping dependencies current and safe

Pinning versions makes builds repeatable, but pinned versions age: security fixes arrive in newer releases, and a project that never upgrades eventually faces one painful jump across many versions. A light routine avoids that. Check for outdated packages with python -m pip list --outdated once a month or so, upgrade a few at a time, run the tests, and commit the new lock file. Run a vulnerability scanner such as pip-audit in continuous integration, so a known issue in any locked package fails the build. And remove packages the code no longer imports; every unused dependency is still something to update and audit.

Notebooks and data projects

Jupyter kernels are tied to environments too. Install ipykernel inside the project's environment and register it, or start Jupyter from that environment, so notebooks import the same package versions as your scripts. Mismatched kernels are the usual cause of "it works in the terminal but not in the notebook"; the Python for data science guide lists the libraries such projects usually pin.

Should I use conda instead?

conda manages environments and non-Python dependencies such as compiled scientific libraries, which some data science setups need. For most application and web projects, venv with pip, Poetry or uv is simpler.

What does pip install -e . do?

It installs your own project into the environment in editable mode: Python imports it from your source folder, so code changes take effect without reinstalling. It is the usual way to develop a package laid out with a pyproject.toml.

Can I move or rename a virtual environment?

Not reliably, because it stores absolute paths. Delete it and create a new one from your requirements or lock file instead.

Do I need a virtual environment inside Docker?

Often not, since the container is already isolated, though some teams keep one for consistency with local development. Either way, install from a lock file with pinned versions.

Pinned environments are what make a model reproducible in a container; see Docker for machine learning. Package management is part of the modules and packages section of the Python Programming course.

Sources

GloryTecks Team

Training & Placement Faculty

The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.

Want to master Python?

Join GloryTecks’ expert-led Python Programming training in Hyderabad with real-time projects and 100% placement support.

Explore course