ProbableOdyssey

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:

  1. A way to execute Python code
  2. A way to download Python packages
  3. 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!

Reply to this post by email ↪