# Wheel: Python Binary Distribution Format


A wheel is a built distribution format for [Python packages](https://pydevtools.com/handbook/explanation/what-is-a-python-package.md), defined by [PEP 427](https://peps.python.org/pep-0427/). It uses the `.whl` extension and is a ZIP archive containing the package's code and metadata, ready for direct installation. Wheels are one of two standard distribution formats on [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md), the other being [sdist](https://pydevtools.com/handbook/reference/sdist.md).

## Why Wheels Matter

Installing from an [sdist](https://pydevtools.com/handbook/reference/sdist.md) requires executing arbitrary build code (`setup.py`, a build backend, etc.) to produce installable artifacts. Wheels skip that step entirely. Because they are pre-built, the installer copies files into place without invoking a compiler or running any build logic.

This has two practical consequences. Installs are faster, often by an order of magnitude for packages with C extensions. And environments without a C compiler -- such as minimal Docker images or CI runners -- can still install compiled packages, as long as a compatible wheel exists on PyPI.

When [pip](https://pydevtools.com/handbook/reference/pip.md) or [uv](https://pydevtools.com/handbook/reference/uv.md) resolves a package, it prefers the wheel over the sdist when one is available.

## Filename Convention

Wheel filenames encode compatibility information using a structured naming scheme:

```
{distribution}-{version}(-{build tag})-{python tag}-{abi tag}-{platform tag}.whl
```

A few examples:

| Filename | Meaning |
|---|---|
| `requests-2.31.0-py3-none-any.whl` | Pure Python, no compiled code, runs on any platform |
| `numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.whl` | Compiled C extensions, CPython 3.12, Linux x86_64 |

The tags break down as follows:

- Python tag (`py3`, `cp312`): the Python implementation and version the wheel supports. `py3` means any Python 3 interpreter; `cp312` means CPython 3.12 specifically.
- ABI tag (`none`, `cp312`): the Application Binary Interface. `none` means no ABI dependency; a CPython-specific tag indicates compiled extensions linked against that version's ABI.
- Platform tag (`any`, `manylinux_2_17_x86_64`, `win_amd64`): the operating system and architecture. `any` means platform-independent.

## Wheel vs sdist

| | Wheel | sdist |
|---|---|---|
| **Format** | ZIP archive (`.whl`) | Compressed tarball (`.tar.gz`) |
| **Contents** | Pre-built code and metadata | Source code and build instructions |
| **Install speed** | Fast (copy files into place) | Slower (must run build step) |
| **Compiler required** | No | Yes, for packages with C extensions |

Most packages on PyPI publish both formats. The sdist serves as a fallback for platforms where no matching wheel exists, and as an archival source-code snapshot.

## Building Wheels

The standard way to build a wheel is through a [build frontend](https://pydevtools.com/handbook/explanation/what-is-a-build-frontend.md) that invokes the project's [build backend](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md):

```bash
# Using uv
uv build

# Using the build package
python -m build
```

Both commands read the `[build-system]` table in [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md) and delegate to the configured backend. Common backends that produce wheels include [setuptools](https://pydevtools.com/handbook/reference/setuptools.md), [hatchling](https://pydevtools.com/handbook/reference/hatch.md), and [flit](https://pydevtools.com/handbook/reference/flit.md).

Built artifacts are placed in the `dist/` directory by default.

## Platform Wheels

Pure Python packages produce universal wheels tagged `py3-none-any`, which work on any platform and Python 3 interpreter. Packages with compiled extensions (C, C++, Cython, Rust) produce platform-specific wheels tied to a particular OS, architecture, and Python version.

On Linux, the [manylinux](https://peps.python.org/pep-0600/) standard defines a set of baseline system libraries that a wheel can depend on. Wheels built to this standard (e.g., `manylinux_2_17_x86_64`) are portable across most Linux distributions. macOS and Windows wheels use their own platform tags (`macosx_11_0_arm64`, `win_amd64`).

The platform tag does not describe CPU instruction sets or GPU requirements, so a single `manylinux_2_17_x86_64` build has to target the lowest-common-denominator x86-64 baseline. [Wheel variants](https://pydevtools.com/handbook/explanation/what-are-wheel-variants.md) (PEP 817 and PEP 825, both in Draft) propose an extension that lets a package ship multiple hardware-specific builds under one name. The same problem affects CUDA wheels, which today work around it with custom index URLs and package-name suffixes (see [Why Installing GPU Python Packages Is So Complicated](https://pydevtools.com/handbook/explanation/installing-cuda-python-packages.md)).

Package maintainers who need to publish platform wheels for multiple targets often use [cibuildwheel](https://pydevtools.com/handbook/reference/cibuildwheel.md) to automate builds across operating systems and Python versions in CI ([how-to guide](https://pydevtools.com/handbook/how-to/how-to-build-multi-platform-wheels-with-cibuildwheel.md)).

## Learn More

- [PEP 427 -- The Wheel Binary Package Format](https://peps.python.org/pep-0427/)
- [Wheel format specification](https://packaging.python.org/en/latest/specifications/binary-distribution-format/)
- [What Are Python Wheels and Why Should You Care?](https://realpython.com/python-wheels/)
- [cibuildwheel documentation](https://cibuildwheel.pypa.io/)
