How to get started with Python with modern tooling using `uv`
The hard part of teaching someone how to do machine learning usually isn’t the machine learning itself – it’s installing Python.
There have been so many articles written over the years on the topic of Pythons imperfect tooling (I doubt I can provide any new insights to this). The reason for Pythons idiosyncrasies appear to be twofold: Python is used by many people with different levels of expertise across a large number of different platforms and environments. What works for the sever admin on Linux must also work for the hobbyist on Windows and the academic researcher on Mac.
I encourage reading what others have written on the topic. In this post I want to answer the question:
I’m interesting in machine learning, but have relatively little Python experience. How can I get started with Python and machine learning
New tooling has improved for python from Astral over the past couple of years,
and it’s made it easier than ever to get into Python from scratch with uv
.
I’ll show you how to use uv
to get right into Python on a fresh Ubuntu 24.04
LTS system. These instructions should also work for Mac as well.
A disclaimer: I primarily work on Linux, and occasionally on Mac. The instructions I give here should work across both these platforms but I cannot provide any insight to Windows unfortunately.
I also assume you’re already a developer who’s proficient with the terminal when I write this article. Here’s what we need to get started with Python:
- A way to execute Python code
- A way to download Python packages
- A way to isolate downloaded packages in virtual environments for each project
All three of these can be solved with the recently released uv
tool
1. Download uv
The uv
team provides a one-line installation script:
# If you use `curl`
$ curl -LsSf https://astral.sh/uv/install.sh | sh
# If you use `wget`:
$ wget -qO- https://astral.sh/uv/install.sh | sh
They also provide methods for using brew
from the core packages if that’s
your jam:
$ brew install uv
Once installed, start a new shell and check it
$ uv --version
# uv 0.6.5
2. Start a new project
I’m going to start a project for our toy project that uses Pytorch:
$ mkdir -p ~/Workspace/proj-pytorch
$ cd ~/Workspace/proj-pytorch
$ uv init .
# Initialised `proj-pytorch` at `/home/user/Workspace/proj-pytorch`
A few files are generated:
$ tree -a .
# .
# ├── main.py
# ├── pyproject.toml
# ├── .python-version
# └── README.md
#
# 1 directory, 4 files
The files main.py
and README.md
are where we’ll start writing code and
documentation.
$ cat main.py
# def main():
# print("Hello from proj-pytorch!")
#
# if __name__ == "__main__":
# main()
Our project configuration will live in pyproject.toml
:
$ cat pyroject.toml
[project]
name = "proj-pytorch"
version = "0.1.0"
description = "Add your description here"
reradme = "README.md"
requires-python = ">=3.12"
dependencies = []
Soon we’ll use uv
to add python packages in our project dependencies – we
wont need to edit this field manually.
The file .python-version
is used by uv
to load and run a
specific version of Python. In this case:
$ cat .python-version
# 3.12
We can use uv
to run the code:
$ uv run python main.py
# Using CPython 3.12.3 interpreter at: /usr/bin/python3.12
# Creating virtual environment at: .venv
# Hello from proj-pytorch!
If the Python version is already installed and discoverable on your system, uv
will automatically use that. If not (lets demonstrate this by using an older
Python I don’t have installed, I’ll edit requires-python = ">=3.11"
in
pyproject.toml
). The selected version of Python will be downloaded:
$ uv python pin 3.11
# Using CPython 3.11.11 interpreter
# Removing virtual environment at: .venv
# Creating virtual environment at: .venv
As we can see from the command output so far, uv
automatically creates a
virtual environment for our project – we don’t even need to think about it!
(just need to remember to .gitignore
the hidden .venv
directory in our
project)
3. Install python packages
Let’s install Pytorch for our project. Python packages are installed with the
built-in package manager pip
. Aside from the more esoteric commands, most
actions you would run with pip
will work with uv
.
The official instructions for installing Pytorch (version 2.6.0
for CUDA 12.4)
tell us to use
$ pip install torch
With uv
, we’ll instead use
$ uv add torch
This will download Pytorch with all of the necessary dependencies (with
concurrency, which pip
does not do), and also adds torch
to the dependency
field of pyproject.toml
. It will also spawn a uv.lock
file so that you can
distribute your code and a user can run uv sync
to replicate your python
environment to replicate your Python environment.
After it’s installed, we can fire up a Python prompt to check it out:
$ uv run python
>>> import torch
>>> print(torch.rand(3))
# tensor([0.2205, 0.3637, 0.8216])
There might be a warning about missing a numpy
dependency. Let’s exit Python
and add it
>>> exit()
$ uv add numpy
Our pyproject.toml
now has these dependencies declared:
dependencies = [
"numpy>=2.2.3",
"torch>=2.6.0",
]
Using package indices other than Pypi
There are different versions of Pytorch published for use with different
accelerators and CUDA versions. For example, if we want the CPU-only version of
Pytorch, we’ll need to download it from their index
https://download.pytorch.org/whl/cpu
. Their instructions say to use
$ pip install torch --index-url https://download.pytorch.org/whl/cpu
To do this with uv
, we’ll append the following sections to our pyproject.toml
:
[tool.uv.sources]
torch = [
{ index = "pytorch-cpu" },
]
[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
Now we can refresh our environment with uv sync
.
Conclusion
We should now have all we need for basic Python development! The documentation
for uv
is extremely well-written and has been developed quite well. I
encourage having a read of their website to learn more
We can now write code in main.py
and execute it with uv run python main.py
,
I might even add alias py='uv ruin python'
to my shell RC file. We can also
start developing code in a library
$ mkdir src
$ touch src/__init__.py
$ touch src/foo.py
The __init__.py
file will tell Python that this is a package we can import
from. We can then add the following content to foo.py
:
import torch
def bar(n):
return torch.rand(n)
Then we can import this into a Python session:
$ uv run python
>>> from src.foo import bar
>>> bar(3)
# tensor([0.5001, 0.0048, 0.6366])
Or we could use this in main.py
:
from src.foo import bar
def main():
print(bar(3))
print("Hello from proj-pytorch!")
if __name__ == "__main__":
main()
Then we can run it with
$ uv run python main.py
# tensor([0.5001, 0.0048, 0.6366])
# Hello from proj-pytorch!
There’s more we can cover, such as types, debugging, formatting and linting, but I’ll post about that another day. For now you should have what you need to get started with any of the wonderful tutorials on Pytorch, or any other python library!