uv (recommended for new projects) is a drop-in replacement for pip + virtualenv + pip-tools + poetry, written in Rust. ~10-100× faster than pip. Manages Python versions too (replaces pyenv). Single tool, no shell activation needed. If you're starting fresh, use this.
pip + venv: the bare-bones option. Use pip-tools (or pip freeze) to produce a lockfile from your requirements.in. Fine for simple projects; uv just makes the same workflow faster.
conda / mamba: when you need non-Python dependencies — CUDA toolkit, system libraries, MKL, R, etc. Heavier than pip but invaluable when the alternative is "install CUDA system-wide". mamba is the fast C++ implementation; almost always use it instead of conda directly.
poetry: was the modern choice before uv. Still solid; uv is faster and more focused. New projects: pick uv. Existing poetry projects: migrate when you have time.
Docker: for deployment and CI, not local development. Start FROM python:3.11-slim, install your locked dependencies, copy your code, set the entrypoint. The container is the unit of "deployable artefact" — even your training jobs should run in one for production work.
Lockfiles matter. pyproject.toml says "I want torch ≥ 2.0"; the lockfile (uv.lock, poetry.lock) records "I got torch 2.4.1, numpy 1.26.4, ...". Commit the lockfile. CI builds from the lockfile. This is what makes "reproducible environment" actually true.