Running a Python script used to mean creating a virtualenv, activating it, installing requirements, and hoping the versions matched. Four tools now compete to make that unnecessary, and they solve genuinely different problems. Here is how they compare when the job is "run this script reliably on someone else's machine".
| Tool | Best at | Lockfile | Gets you |
|---|---|---|---|
| pip + venv | Baseline, always present | requirements.txt (manual) | Zero install, maximum manual work |
| uv | Speed, single-file scripts | uv.lock | Resolve + install in seconds, manages Python itself |
| pipx | Installing CLI apps | none | Each tool in its own venv, on your PATH |
| Poetry | Packaging a library | poetry.lock | Dependency groups, build and publish |
uv is a Rust reimplementation of the pip and virtualenv workflow, and the speed difference is not marginal — resolving and installing a dependency set that pip works through in a minute typically finishes in a few seconds, because uv resolves in parallel and hardlinks from a global cache instead of re-downloading wheels. It will also install and pin the interpreter: uv python install 3.12 then uv run --python 3.12 script.py.
PEP 723 lets a script declare its own dependencies in a comment block:
#!/usr/bin/env -S uv run --script# /// script# requires-python = ">=3.11"# dependencies = ["httpx", "rich"]# ///
Now ./report.py works on a machine with no venv, no requirements file and no setup instructions — uv reads the header, builds an ephemeral environment and runs it. For one-off scripts you hand to colleagues, this is the single biggest quality-of-life change in the ecosystem. uv run --with pandas script.py does the same thing ad hoc.
pipx installs applications, not libraries: pipx install ruff puts ruff on your PATH in an isolated environment so it cannot conflict with your project's dependencies. pipx run cookiecutter executes without installing at all. uv covers this with uv tool install, so if you have adopted uv you can drop pipx; if you have not, pipx is still the correct answer for CLI tools and takes thirty seconds to learn.
If you publish to PyPI, Poetry's dependency groups, version constraint solving and poetry publish remain a coherent package. For a directory of internal scripts it is heavier than the job requires, and its resolver is noticeably slower on large trees. Hatch is the closest alternative if you want standards-first pyproject.toml without Poetry's opinions.
Single scripts or a team repo of automation: uv, with PEP 723 headers on standalone files and uv.lock committed for projects. Command-line tools you use everywhere: uv tool install or pipx. A library you publish: Poetry or Hatch. Plain pip and venv remain the right answer in one situation — a locked-down environment where you cannot install another binary, in which case pin everything with pip freeze and hash-check with pip install --require-hashes.