pyguides

pip vs Poetry vs uv: Package Managers Compared

Three tools dominate Python dependency management in 2026: pip, Poetry, and uv. The pip vs Poetry vs uv choice is less about which is fastest and more about which trade-offs fit your project: scope, speed, and lockfile strictness.

What each tool actually is

pip is the installer bundled with CPython. It downloads and installs packages from indexes, full stop. Project management, virtualenvs, lockfiles, and Python version management are not its job. Per the pip docs: “pip is not a workflow management tool.”

Poetry is a project manager. It wraps dependency resolution, virtualenv creation, packaging, and publishing into one CLI. The catch is its own TOML schema ([tool.poetry.*]) instead of the standard PEP 621 [project] table most of the rest of the ecosystem uses.

uv is a single Rust binary from Astral that aims to replace pip, pip-tools, pipx, virtualenv, pyenv, and most of Poetry. It reads standard PEP 621 pyproject.toml, has a cross-platform lockfile, manages Python versions, and is the fastest of the three by a wide margin.

Quick comparison

TaskpipPoetryuv
Init project(none)poetry new myappuv init myapp
Add runtime deppip install requestspoetry add requestsuv add requests
Add dev deppip install -r dev-requirements.txtpoetry add --group dev pytestuv add --dev pytest
Install everythingpip install -r requirements.txtpoetry installuv sync
Run a command in venvactivate venv firstpoetry run python app.pyuv run python app.py
Build wheel + sdistpython -m buildpoetry builduv build
Publish to PyPItwine upload dist/*poetry publishuv publish
Install a Python version(use pyenv)(use pyenv)uv python install 3.12
Cross-platform lockfilenoper-Python onlyyes (uv.lock)

pip: the baseline

pip ships with Python, so there is nothing to install. The typical workflow pairs it with the stdlib venv module and a requirements.txt file:

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

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

pip freeze is a pin list, not a true lockfile. It captures what is installed in this environment on this OS for this Python. Cross-platform reproducibility is not guaranteed. pip lock exists in pip 26.x and follows PEP 665, but the ecosystem has not adopted it, so do not treat pip’s lockfile story as solved.

Where pip is the right call: short scripts, learning Python, and any project that already leans on requirements.txt with no plans to change. It is the floor: always available, always works, no extra tooling to learn.

Poetry: the project manager

Poetry’s strength is one CLI for the whole project lifecycle, from scaffolding to PyPI upload:

poetry new myapp
cd myapp

poetry add requests rich
poetry add --group dev pytest ruff

poetry install
poetry run python -m myapp

You get an auto-created virtualenv, a poetry.lock with hashes, and poetry build / poetry publish for shipping to PyPI. The pyproject looks like this:

[tool.poetry]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.10"

[tool.poetry.dependencies]
python = ">=3.10"
requests = "^2.31"
rich = ">=13"

[tool.poetry.group.dev.dependencies]
pytest = "^8"
ruff = "^0.5"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Two things to know before committing. First, Poetry’s ^ and ~ are not pip syntax: ^2.31 means >=2.31, <3.0 and only Poetry interprets it. Second, the [tool.poetry] schema is fine but not PEP 621, so other backends (hatch, flit, setuptools) will not see those dependencies. Migration out of Poetry means rewriting that block.

uv: the unified tool

uv reads standard PEP 621 metadata, so its pyproject.toml looks like what pip and hatch already understand:

[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
    "requests>=2.31",
    "rich>=13",
]

[dependency-groups]
dev = [
    "pytest>=8",
    "ruff>=0.5",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

After uv init writes the project skeleton, the rest of the day-to-day workflow looks like this in practice. From a fresh checkout you install uv, scaffold a project, add a runtime dep and a dev dep, sync the lockfile, then run the entry point:

curl -LsSf https://astral.sh/uv/install.sh | sh

uv init myapp
cd myapp

uv add requests rich
uv add --dev pytest ruff

uv sync
uv run python -m myapp

uv run creates the .venv on first use and runs inside it, so you can skip the activate/deactivate dance entirely. You can also pin a Python version per project, and the pin sticks to that directory rather than leaking into your shell session:

uv python install 3.12
uv python pin 3.12
uv run --python 3.11 python -c "import sys; print(sys.version)"

One feature nobody else has is PEP 723 inline script metadata. A single .py file can declare its own dependencies, which is handy for ad-hoc fetchers, one-off ETL scripts, and reproducible examples you can hand to a colleague without a setup ritual:

# /// script
# requires-python = ">=3.10"
# dependencies = [
#   "requests>=2.31",
# ]
# ///
import requests
print(requests.get("https://pypi.org").status_code)

Run it with uv run and the tool resolves and installs the inline deps on first use, then executes the script in the same ephemeral environment:

uv run fetch_pypi.py
# Installed 1 package in 12ms
# 200

uv also ships a pip-compatible interface, which is useful for older projects that still ship requirements.txt and for teams that want the speed without the project-mode migration:

uv venv
uv pip compile requirements.in --universal --output-file requirements.txt
uv pip sync requirements.txt

How much faster is uv?

The headline number, from Astral’s published benchmarks: uv is roughly 10-100x faster than pip for installs and resolution, depending on cache state and package mix. The benchmark target is Trio’s docs-requirements.in on macOS with Python 3.12.4, and the gap shrinks dramatically when packages have to be built from source (e.g. numpy from an sdist).

OperationpipPoetryuv (warm)uv (cold)
Install ~50 deps, warm~8 s~6 s~0.3 s
Install ~50 deps, cold~30 s~25 s~5 s
Resolve / lockn/a~3 s warm / ~10 s cold~0.5 s warm~1 s cold

Treat those as order-of-magnitude. Your numbers will differ based on OS, filesystem, and dependency tree. The takeaway still holds: uv is fast enough that your CI logs no longer have to budget for the install step.

Do you need a lockfile?

ToolLockfileCross-platformHashes
piprequirements.txt (via pip freeze)nono (use pip-compile --generate-hashes)
pippip lock (PEP 665)yesyes, but limited ecosystem support
Poetrypoetry.lockper-Pythonyes
uvuv.lockyes (one lockfile, many platforms)yes

The practical win for uv.lock is monorepos and app code that ships to multiple OS/arch combinations. One lockfile resolves to all of them, and you stop getting “works on my machine” reports from CI.

Which should you use?

  • Library author shipping to PyPI. Poetry or uv. Either is fine; pick based on whether you want to stay on Poetry’s TOML schema or PEP 621.
  • App/team that wants fastest CI. uv. The install time savings on every CI run add up.
  • Beginner or minimal config. pip + venv + requirements.txt. Nothing extra to learn.
  • Existing Poetry project. Stay on Poetry until you have a real reason to migrate. The rewrite is not free.
  • Need to manage Python versions and CLIs in one place. uv. Nothing else does it.

How do you migrate from pip or Poetry to uv?

The paths below cover the common moves. None of them require a big-bang rewrite; you can do them in a single PR and roll back if anything breaks.

If you are moving from pip to uv, uv pip install -r requirements.txt works as a drop-in for pip install -r requirements.txt. For project workflows, uv add can read a requirements.txt and convert it into a pyproject.toml entry plus a uv.lock.

When moving from Poetry to uv, uv add works if pyproject.toml is already PEP 621. If you have [tool.poetry], rewrite that block to [project] first, because uv ignores the Poetry tables.

If you are going from pip to Poetry, poetry add $(cat requirements.txt) works for a quick start. After that, split runtime and dev groups by hand to keep the lockfile clean.

What trips people up?

  • pip search is gone. Use the PyPI search page or pip index versions <pkg>.
  • pip lock (PEP 665) exists but is not broadly supported. If you need a real lockfile today, use uv lock or poetry lock.
  • Poetry’s ^ and ~ are Poetry-only. PEP 621 metadata uses >=X,<Y+1 style constraints.
  • [tool.uv] is ignored by pip, Poetry, hatch, and friends. Keep it minimal if portability matters.
  • The [build-system] block is what actually picks your build backend. uv add hatchling does not switch your builder; you have to edit [build-system] by hand.

Frequently asked questions

Should I switch from pip to uv?

Not necessarily. pip still works, ships with Python, and is fine for short scripts and existing projects that already use requirements.txt. uv shines when you want faster installs, a real lockfile, and unified tooling. Test it on a side project first and migrate once you are confident the workflow fits.

Is Poetry still worth learning in 2026?

Yes, especially for library authors. Poetry’s CLI is mature, the workflow is well-documented, and many tutorials assume it. The trade-off is that the [tool.poetry] schema is not PEP 621, so portability to other build backends (hatch, flit, setuptools) requires rewriting that block.

Does uv work with existing requirements.txt projects?

Yes. uv pip install -r requirements.txt is a drop-in for pip install -r requirements.txt. The bigger win is uv pip compile requirements.in --universal --output-file requirements.txt, which produces a cross-platform lockfile from a small set of top-level requirements. You can verify what uv resolved with uv pip show requests afterwards.

Can uv replace pyenv?

Yes. uv python install 3.12, uv python pin 3.12, and uv run --python 3.11 cover the same ground as pyenv, with the bonus of being integrated with the rest of the workflow. You can keep pyenv if you have muscle memory, but uv will do the job.

What is the best Python package manager in 2026?

There is no single answer. pip is the safest default, Poetry is the most ergonomic for library authors, and uv is the fastest and most unified. The right choice depends on your project: a one-off script wants pip; a multi-year library wants Poetry or uv; a CI-heavy monorepo wants uv.

Conclusion

pip is the floor: it always works, ships with Python, and stays out of the way. Poetry is the project manager for people who want one tool to do init through publish and do not mind a non-PEP 621 schema. uv is the speed-and-unification play: one Rust binary, PEP 621, cross-platform lockfiles, Python version management, and the fastest installs in the ecosystem. The pip vs Poetry vs uv decision comes down to which trade-offs match your project, not which tool is on top of a Hacker News thread this week.

See Also