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
Last updated: 24 September 2026
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
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 itActivation 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.
$ .venv/Scripts/python -c "import sys; print(sys.prefix != sys.base_prefix)"
True
$ python -c "import sys; print(sys.prefix != sys.base_prefix)"
FalseWhen 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:
$ 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.txtOne 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 dependencies | Locked dependencies | |
|---|---|---|
| What | The packages your code imports | Every package installed, including dependencies of dependencies |
| Versions | Ranges you support, such as requests>=2.31 | Exact versions, such as requests==2.32.3 |
| Where | pyproject.toml dependencies, or a short requirements.in | requirements.txt from pip freeze, or a lock file |
| Changes when | You decide to use or drop a library | You 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):
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 environmentCommit 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
- One environment per project, in
.venv, never committed. - Install with
python -m pip, from inside the environment. - Keep direct dependencies in
pyproject.toml(see how to structure a Python project) and exact versions in a lock or requirements file. - Recreate rather than repair: when an environment is in a strange state, delete
.venvand reinstall from the lock file. It takes a minute and removes every doubt. - 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?
What does pip install -e . do?
Can I move or rename a virtual environment?
Do I need a virtual environment inside Docker?
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
- venv — Creation of virtual environments — Python documentation
- pip User Guide — pip.pypa.io
- Installing Packages (Python Packaging User Guide) — Python Packaging User Guide
- Poetry: basic usage — Poetry documentation
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.
Related articles
Learn Python from Scratch: A Complete 2026 Roadmap
How to learn Python from zero: install a current version (including the new Windows install manager), then work through seven stages, from values and loops to files, virtual environments, classes and tests, each ending with a small program you should be able to write on your own.
Python Interview Questions: Top 50 with Answers
Fifty conceptual Python interview questions with short, precise answers, grouped by topic: basics, types and mutability, data structures, functions and scope, OOP, iterators and decorators, errors and files, and internals, with runnable examples for the ones people most often get wrong.
Python for Data Science: The Essential Libraries
The Python data science stack explained by the job each library does: NumPy for arrays, pandas for tables, Matplotlib and seaborn for charts, SciPy and statsmodels for statistics, scikit-learn for models and Jupyter for exploration, with one runnable example that passes a real dataset through them.