For years, python -m venv .venv has been the default answer to Python dependency management and isolation. It works, it’s built into Python and almost every tutorial assumes you’ll use it.
But today, more and more developers are asking a question Do I actually need venv anymore?
And the answer is: not always. This article explores what modern alternatives exist and when they make sense.
Option 1: uv
The biggest momentum shift in Python tooling right now is probably uv. It’s a Rust-based Python package manager that aims to be extremely fast while simplifying dependency and environment management.
To install uv, invoke:
curl -LsSf https://astral.sh/uv/install.sh | shNow you can call:
uv init my_app
cd my_appThis creates a project folder with the following files:
.python-version– contains the Python interpreter versionmain.py– a ready-to-run hello world applicationpyproject.tomlREADME.md
You can run the generated application with:
uv run main.pyIf you want to add the requests library, call:
uv add requestsAt this point, uv automatically creates a .venv folder and installs the dependency there.
Option 2: Poetry
Before uv, Poetry was often considered the “modern Python workflow” tool. To install Poetry, call:
curl -LsSf https://install.python-poetry.org | python3 -Now you can call:
poetry new myapp
cd myappThis creates a slightly different project structure than uv:
src/myapp/__init__.pytests/__init__.pypyproject.tomlREADME.md
Notice that there is no .venv folder inside the project directory. By default, Poetry stores all virtual environments centrally in one location. On Linux, you can usually find them in ~/.cache/pypoetry/virtualenvs where every Poetry-managed project gets its own dedicated virtual environment.
If you want to add the requests library, call:
poetry add requestsTo run your application, invoke:
poetry run python src/myapp/main.py
AI is powerful. Snippets are instant.
Stop prompting for the same patterns repeatedly. Get almost 100 free VS Code snippets for C++, Python, CMake and Bazel from piko::snippets GitHub repository.
Option 3: PDM
PDM originally implemented an idea based on PEP 582. Instead of creating isolated virtual environments with separate interpreter binaries, dependencies (and only Python dependencies) would simply be stored in a local __pypackages__ directory inside the project.
To install PDM, invoke:
curl -LsSf https://pdm-project.org/install-pdm.py | python3 -Now you can call:
pdm new myappUnlike uv and Poetry, PDM asks several interactive questions before creating the project, for example:
- what Python interpreter should be used?
- what is the project name?
- what is the project version?
- what license should be used?
- what is the author name?
- what is the email address?
- should a Git repository be initialized?
After answering these questions, PDM creates a structure similar to:
.venv.pdm-pythonpyproject.toml
If you want to add the requests library, call:
pdm add requestsAnd the dependency lands in the .venv folder. So where is the promised __pypackages__ directory?
The answer is that PEP 582 was never accepted as an official Python standard, so ecosystem tooling never fully adopted it. As a result, modern PDM versions switched to .venv as the default approach.
If you still want to use __pypackages__, you must enable it explicitly:
pdm config python.use_venv falseNow dependencies will be stored in the __pypackages__ directory instead. Running your app is no surprise at this point:
pdm run main.pyOption 4: Containers instead of environments
Some teams skip Python environments entirely and isolate applications using containers. In this approach, dependency isolation happens at the container level rather than through local virtual environments.
In such setups, having a dedicated venv becomes less important because the container itself acts as the isolation boundary.
So are these really alternatives to venv?
Not really.
Most of these systems still rely on virtual environments in one form or another. The biggest shift is not abandoning venv, but changing how developers think about Python projects.
Traditional venv workflows treat virtual environments as standalone entities that developers explicitly create, activate and attach to projects. Modern tooling flips that model around: the project becomes the primary unit and the environment is automatically associated with it, so instead of thinking “I need to activate this environment.”, developers think “I’m working on this project.” while the tooling quietly ensures that the correct interpreter and dependencies are available behind the scenes.
Read also:
- If not Python venv, then what?
- C++ AI-generated code that looks fast, but is actually slow
- 10 VS Code snippets essential for every Python developer
- Bug of the week #13
- git log tricks you really should know
- Surprisingly necessary explanation of Python venv
- When AI gets C++ bitmask enums almost right
- 10 VS Code snippets essential for every C++ engineer
- Storing your ATTiny program in…EEPROM?
- Bug of the week #12
- Sharing variable between bash scripts
- A 40-line LLM-based bash command executor in Python
- GTest and short-circuit evaluation in C++
- AI is powerful. Snippets are instant.
- From AUTOSAR to S-Core: the first C++ pub/sub implementation
- How to write Arduino Uno code with Python?
- Combining Bazel with Docker
- Running commands with timeout on Linux
- Running Python unit tests with CMake
- Thirdparty dependencies with FetchContent
- Bug of the week #11
- Combining CMake with Docker
- How to search the internet from Linux terminal?
- Folding expressions in C++
- How to derive from an enum in Python?
- Bug of the week #10
- Trying ROS2: client/server within a single container
- Make C++ a better place #4: Go as an alternative
- How to convert hex to dec in Linux terminal?
- Setting up a Python project with CMake
- Separating builds for different configs with Bazel
- Trying ROS2: pub/sub within a single container
- Bug of the week #9
- UDP multicasting with Python
- Destruction order vs thread safety in C++
- Let’s review some code: C++ #2
- Make C++ a better place #3: D as an alternative
- Registering callback using std::function in C++
- Bug of the week #8
- TCP client/server with Python
- Simple menus in Bash scripts with select
- Calling member function on a nullptr in C++
- Bug of the week #7
- Python lru_cache explained
- How to dockerize a Python application?
- Make C++ a better place #2: CppFront as an alternative
- Parameters combinations in GoogleTest
- Data transfer with curl
- Python reduce explained
- Bug of the week #6
- Custom literals in C++
- Linux and hash command
- 5 Python good practices which make life easier
- Let’s review some code: Python #1
- Make C++ a better place #1: What does better mean
- Enums vs enum class in C++
- Bug of the week #5
- UDP client/server with Python
- Hard links in Linux
- Functions calling order in unit tests in C++









