How does the Windows `py` launcher decide which installed Python version to run?
answer
- One command, many installations
- Windows has no kernel shebang handling
- An explicit version argument wins
- It enumerates registered installations
- `py -0` prints what is installed
basics
~20 spy is a single command that dispatches to one of several Python installations registered on the machine. It obeys an explicit version argument such as py -3.13, otherwise a script's shebang line, an active virtual environment, or its configured default — normally the newest installed Python 3.
solid answer
~50 sOn Windows there is one `py` command on `PATH` no matter how many Pythons are installed, and it chooses at run time. Precedence runs roughly: an explicit version argument (`py -3.13 script.py`) wins; otherwise, if a virtual environment is active, that environment's interpreter is used; otherwise, when you run a script file, the launcher reads its shebang line as text and maps the familiar Unix spellings onto an installed version; otherwise it falls back to its configured default, which on current launchers is the newest installed Python 3. `py -0` lists what is installed and marks the default. Two practical consequences: `py -3.13 -m pip install ...` is the reliable way to install into a chosen version on Windows, and typing `python` on a machine with no Python installed can hit a store stub that is not an interpreter at all.
code
console · 2 linespy -0
py -3 -c "import sys; print(sys.executable, sys.version_info[:2])"go deeper
Know that on Windows py is one command that can start any installed Python, and that py -3.13 script.py asks for a specific version. Recall py -0 as the way to see what is installed.
Explain the selection order — explicit version argument, active virtual environment, the script's shebang read as text, then the configured default — and that the launcher works from registered installations rather than from PATH.
Turn it into determinism: pin the version at the call site in build and job definitions with py -3.13 -m ..., know that the launcher and a bare python can disagree on the same machine, and recognise the store-alias and 32-versus-64-bit failure reports on sight.
Decide the fleet-wide convention: whether Windows machines standardise on the launcher with pinned versions or on per-project environments invoked by path, and make developer setup and CI agree so a build never depends on which installer ran last.
## Why Windows needs a launcher and Unix does not On Unix, several interpreters coexist because each has a distinct file name and lives on `PATH`; a script's shebang is executed by the kernel. Windows has neither convention: the kernel does not read shebangs, and installers historically fought over the name `python.exe` on `PATH`. The launcher exists to fix both. It is a small program installed once, at a fixed location that is on `PATH`, that knows about every registered Python installation on the machine and dispatches to one of them. “Registered” is the key word. Windows installers record installed interpreters in a well-known place in the system registry, with the version, architecture and installation path. The launcher enumerates that registry rather than scanning directories, which is why it can list installations that are not on `PATH` at all — and why an interpreter unpacked by hand, or one that deliberately does not register itself, is invisible to it. ## The selection order Roughly, in decreasing priority: 1. **An explicit version argument.** `py -3.13 script.py` demands that version; `py -3` means the newest installed 3.x. If the requested version is not installed, the launcher fails loudly rather than silently picking a neighbour — which is exactly the behaviour you want in a build script. 2. **An active virtual environment.** Current launchers defer to the environment you are working in rather than the machine default. 3. **The script's shebang line.** When you run `py script.py`, the launcher reads the first line as text and maps the familiar Unix spellings — including the `/usr/bin/env python3` form — onto an installed version. This is a text-level emulation, not the kernel behaviour of Unix: nothing on Windows executes that path. 4. **Configuration and defaults.** Failing all of the above, the launcher uses its configured default, which on current versions is the newest installed Python 3. It can be configured, per user or per machine, to prefer a specific version. `py -0` (also spelled `py --list`) prints the installed versions with the default marked — the first command to run when you are unsure what a machine actually has. ## Why this matters for installs, not just for running Everything the launcher does is about **which interpreter starts**. Combine that with the rule that pip should be invoked through the interpreter you mean, and the reliable Windows install command falls out: `py -3.13 -m pip install <name>`. It names the version, that version runs its own pip, and the packages land in that version's `site-packages`. A bare `pip` on Windows has the same shebang-and-`PATH` ambiguity as anywhere else, aggravated by the fact that several installers may have contributed a `Scripts` directory to `PATH`. ## The classic Windows confusions **The store stub.** A fresh Windows install ships an app-execution alias named `python` that is not an interpreter; running it opens a store page. A user reports “python does nothing” and the fix is to install a real interpreter, or use `py`, or turn the alias off — not to debug the interpreter. **Two commands, different answers.** `python` is resolved by `PATH` order and depends on which installer ran last and which options it was given; `py` is resolved by the launcher's own rules. On the same machine they can start different interpreters, which produces the familiar “it works in one terminal and not the other” report. **Architecture.** A 32-bit and a 64-bit build of the same version can both be installed; the launcher can distinguish them, and a package with compiled components installed into one is not importable from the other. **Portability of scripts.** Because the launcher reads shebangs as text, a script written with a Unix-style shebang keeps working when it is run as `py script.py` on Windows. That is genuinely useful for cross-platform tooling — but it only applies when the launcher starts the script, not when the file is double-clicked through a file association or invoked through some other harness. ## What to say in an interview The answer worth giving is not the precedence list recited from memory; it is the reasoning: on Windows the interpreter is chosen by a dispatcher rather than by `PATH` and a kernel, so the way to be deterministic is to name the version at the call site (`py -3.13 -m ...`), and the way to find out what you have is `py -0`.
- On Windows, what is the reliable way to install a package into a specific Python version?Name the version and let it run its own pip: `py -3.13 -m pip install <name>`. The launcher starts exactly that interpreter, `-m` uses the pip installed alongside it, and the files land in that version's `site-packages`. A bare `pip` on Windows is a script in some `Scripts` directory that PATH happened to find first, and several installers may have contributed one.
- Why can `python` and `py` start different interpreters on the same Windows machine?They are resolved by different mechanisms. `python` is an ordinary PATH lookup, so it depends on which installer ran last and whether it was allowed to modify PATH — and on a machine with no Python it can hit a store alias that is not an interpreter at all. `py` ignores PATH and applies the launcher's own rules over the registered installations. Discrepancies between two terminals almost always trace back to this.
saying these in an interview costs you the question
- Thinks `py` and `python` are the same executable
- Believes Windows honours shebangs at the OS level
- Assumes `py` always runs the oldest installed version
- Expects a hand-unpacked interpreter to appear in `py -0`
- Says a bare `pip` on Windows is unambiguous