skip to content

What is the difference between a Python sdist (.tar.gz) and a wheel (.whl)?

level: juniorimportance: must knowfreq 65%

answer

  1. Two things a project can upload
  2. One must be built, one just unzips
  3. A zip with a metadata directory
  4. The filename decides where it may install
  5. No build backend runs at install time

basics

~20 s

An sdist is a source tarball that has to be built on the installing machine; a wheel is a pre-built zip that the installer only unpacks into site-packages. Wheels install in seconds and need no compiler.

solid answer

~50 s

A **source distribution** is a `.tar.gz` of the project's source tree plus a `PKG-INFO` metadata file. Installing it is a build: pip unpacks it, reads the build-system table from `pyproject.toml`, provisions the build requirements it names, calls the backend to make a wheel, and installs that. So the machine needs whatever the build needs — for a project with C in it, a compiler and headers. A **wheel** is a `.whl` zip in a fixed layout: the package directories exactly as they land in site-packages, plus a `.dist-info` directory holding the metadata, a record of installed paths and their hashes, and the wheel-format marker. Installing it is essentially an unzip plus generating console-script wrappers; no project build code runs. The filename carries compatibility tags — `py3-none-any` for pure Python, an interpreter/ABI/platform triple otherwise — and pip falls back to the sdist only when no wheel matches.

code

python · 8 lines
python
import pathlib
import zipfile

for whl in sorted(pathlib.Path("dist").glob("*.whl")):
    with zipfile.ZipFile(whl) as zf:
        names = zf.namelist()
    print(whl.name, len(names), "entries")
    print([n for n in names if n.endswith("/METADATA")])

go deeper

for a junior

Be ready to say in one breath that a wheel is pre-built and unzipped while an sdist is source that gets built on your machine, and to name the file extensions .whl and .tar.gz.

for a middle

Explain the mechanics: what the installer does for each format, that a source install provisions build requirements and calls a backend, and that a wheel's filename tags decide whether it may be installed at all.

for a senior

Show you can read an install log and tell a wheel install from a source build, and connect a slow or failing install in a minimal image to a tag miss plus a missing toolchain rather than guessing at the package.

for a principal

Own the policy: which artefacts your organisation publishes and consumes, whether source builds are permitted in production pipelines at all, and what a wheel-only supply chain costs you in auditability and platform coverage.

### Two artefacts, one distinction A Python project can publish exactly two kinds of artefact, and almost everything people find confusing about installing Python code follows from the difference between them: one is **source that must be built**, the other is **a build result that is merely copied into place**. ### The sdist A source distribution is a gzipped tar archive — `name-version.tar.gz` — containing the project's source tree as the author chose to ship it, plus a generated `PKG-INFO` file carrying the core metadata (name, version, requirements, summary). Because it is source, *installing it is a build*. The installer unpacks the tarball into a temporary directory, reads the build-system table in `pyproject.toml` to learn which build backend to use and which packages that backend needs, provisions those in a build environment, and asks the backend to produce a wheel. Only then does it install the wheel it just built. That build has to succeed on **your** machine. For a pure-Python project it usually does: the backend copies files and writes metadata. For a project with C, C++, Rust or Fortran in it, the build needs a matching compiler toolchain, the interpreter's development headers, and the headers and libraries of whatever it links against. This is why an install that normally takes two seconds can take four minutes, and why it can fail on a slim container image with a compiler error that mentions no Python at all. ### The wheel A wheel is a zip file named `name-version[-build]-interpreter-abi-platform.whl`. Inside is a layout defined by the wheel specification: the importable package directories laid out exactly as they should appear in `site-packages`, and a `.dist-info` directory holding the metadata file, a record listing every installed path with its hash and size, a wheel-format marker naming the generator and whether the payload is pure Python, licence files, and the entry-points declaration used to create console scripts. Installing a wheel is a data operation: verify the archive, unpack the payload into the right site-packages root, generate the console-script wrappers described by the entry-points declaration, and write the record file that makes the distribution discoverable to `importlib.metadata` afterwards. **No build backend runs and no project build code executes at install time.** That is the entire point of the format. ### Compatibility lives in the filename Because a wheel is pre-built, it is only valid where its build assumptions hold, and the filename says where. A pure-Python project ships a single `py3-none-any` wheel that installs on every interpreter and platform. A project with compiled code ships one wheel per interpreter/ABI/platform combination it supports, so the same release may carry a dozen files. The installer computes the set of tags its own environment supports, ranks the available wheels against that set, and picks the best match; if nothing matches it downloads the sdist and builds. ### Why wheels exist at all Historically, installing a Python package meant downloading source and executing the project's build script as whatever user ran the installer. Every install was a build, so every install was slow, needed a toolchain, could run arbitrary code, and could produce a subtly different result on each machine. Wheels move that build to the publisher's machine, where it happens once, is reproducible, and can be inspected before it is uploaded. The install side then becomes a predictable unzip. ### Where the sdist still earns its place The sdist is the fallback and the source of record. It covers interpreters and architectures the publisher never built for — a brand-new Python release, an unusual CPU, a platform with no published wheel. It is what distribution packagers and rebuild pipelines consume, because they must build everything from source themselves. It is what an auditor reads and what you patch when you need a one-line fix in a dependency. And it usually contains material the wheel does not: the test suite, build scripts and configuration are build inputs, not install outputs, so they typically ship in the sdist and are stripped from the wheel. ### What this buys you in practice Knowing which artefact you are installing explains three everyday symptoms. An install that suddenly compiles for minutes is a wheel-tag miss falling through to the sdist. A `no matching distribution` error usually means neither a compatible wheel nor a usable sdist exists for your interpreter or platform. And a dependency that installs cleanly on a developer laptop but fails in a minimal CI image is almost always a source build hitting a missing compiler or missing headers — a build-environment problem, not a Python one.

  • What is in a wheel's .dist-info directory, and what is it for after installation?
    The metadata file (name, version, dependencies, classifiers), a record of every installed path with its hash and size, a wheel-format marker naming the generator, licence files, and the entry-points declaration. The installer copies that directory into site-packages, where it becomes the installed-distribution record that `importlib.metadata` reads for `version()`, `entry_points()` and dependency introspection, and that the uninstaller uses to know what to delete.
  • Does installing a wheel instead of an sdist mean no untrusted code runs?
    No. It removes code execution at *build* time, which is the big win, but the package's code still runs the moment you import it, and a wheel may ship a `.pth` file whose contents execute at interpreter startup. Wheels make installs fast and reproducible; they are not a sandbox, and the trust decision about a dependency is unchanged.
  • How does pip decide between a wheel and the sdist when a release offers both?
    It computes the tags its environment supports, ranks the release's wheels against that list and installs the best match. If no wheel tag matches, it downloads the sdist and builds. `--only-binary=:all:` turns that fallback into a hard failure, and `--no-binary=:all:` forces the source path even when a wheel exists.

An sdist is a flat-pack kit: cheap to ship anywhere, but you supply the tools and the hour. A wheel is the assembled furniture, delivered ready to place — only in the sizes the factory actually built.

saying these in an interview costs you the question

  • Says a wheel is just a renamed sdist
  • Thinks every pip install compiles the package
  • Believes the sdist installs faster because it is smaller
  • Cannot say what actually runs at install time for each format
  • Assumes one wheel works on every interpreter and platform
  • Thinks a wheel contains the project's test suite and build scripts

context