How does .venv/bin/python know it is running inside a virtual environment?
answer
- The interpreter figures it out by itself
- It starts from its own executable path
- A small text file one directory up
- pyvenv.cfg and its home key
- sys.prefix differs from sys.base_prefix
basics
~20 sAt startup the interpreter resolves the path of its own executable and looks for a pyvenv.cfg file beside its directory. Finding one, it sets sys.prefix to the environment, records the original installation in sys.base_prefix, and uses the environment's site-packages.
solid answer
~40 sNothing in the shell tells it — the interpreter works it out from its own location. CPython resolves `sys.executable`, walks up from the `bin` (or `Scripts`) directory, and if a `pyvenv.cfg` sits there it switches to virtual-environment mode: `sys.prefix` becomes the environment root, `sys.base_prefix` keeps the installation that created it, and `site` puts the environment's `lib/python3.14/site-packages` on `sys.path`. The file itself is a few `key = value` lines: `home` (the base interpreter's directory), `include-system-site-packages`, `version`, and on current CPython also the creating `executable` and the exact `command`. Crucially the standard library still comes from the base installation named by `home`, so only third-party packages are private to the environment. The reliable in-code test is `sys.prefix != sys.base_prefix`, never the `VIRTUAL_ENV` variable.
code
python · 7 linesimport sys
if sys.prefix == sys.base_prefix:
print("base interpreter:", sys.prefix)
else:
print("virtual environment:", sys.prefix)
print("built from:", sys.base_prefix)go deeper
Remember that the environment directory holds a pyvenv.cfg file and its own site-packages, and that the interpreter reads that file at startup. Being able to point at the file and say what it is for is enough here.
Explain the startup sequence: executable path, pyvenv.cfg one directory up, sys.prefix versus sys.base_prefix, and the standard library still coming from the base installation named by home.
Use this to diagnose. When an import resolves to the wrong package, print sys.executable, sys.prefix and sys.base_prefix before touching anything, and know that a missing or edited pyvenv.cfg silently changes which packages a service sees.
Decide how environments are identified and audited across services — a standard way to report the interpreter and prefix a process is running under is what makes a fleet of environments diagnosable instead of guessable.
### The question the interpreter asks itself at startup A virtual environment is not a mode you switch on; it is a conclusion the interpreter reaches during its own initialisation. Very early in startup CPython computes where it is installed, and that computation begins from the path of the running executable. It resolves that path, takes the directory containing it (`.venv/bin` on POSIX, the `Scripts` directory on Windows), steps up one level, and looks for a file called `pyvenv.cfg`. This is the whole of PEP 405, the design that shipped in Python 3.3 and that `venv` still implements. If no such file exists, the interpreter is a plain installation: `sys.prefix` and `sys.base_prefix` are the same. If the file *does* exist, the interpreter enters virtual-environment mode: `sys.prefix` (and `sys.exec_prefix`) become the environment directory, while `sys.base_prefix` and `sys.base_exec_prefix` keep the original installation. The `site` module then constructs the site-packages path under `sys.prefix` and adds it to `sys.path`. ### What is actually in pyvenv.cfg On CPython 3.14 a freshly created environment holds something like: ``` home = /usr/local/bin include-system-site-packages = false version = 3.14.0 executable = /usr/local/bin/python3.14 command = /usr/local/bin/python3.14 -m venv /srv/app/.venv ``` `home` is the load-bearing key: it names the directory of the base interpreter, and it is how the environment finds the standard library. `include-system-site-packages` decides whether the base installation's site-packages is appended to `sys.path` as well. `version` records the interpreter version the environment was built for. The `executable` and `command` keys are provenance recorded by recent CPython — useful when you are reverse-engineering how an environment on a server came to exist. An environment created by an older interpreter may carry only the first three keys. Because this is a plain text file the interpreter reads at startup, editing it takes effect on the next run — flipping `include-system-site-packages` to `true` really does add the base site-packages to `sys.path`. Deleting the file is equally consequential: the same executable then stops detecting an environment, `sys.prefix` collapses to the base installation, and the environment's packages vanish from `sys.path`. ### What is shared and what is private This is where candidates most often go wrong. A virtual environment does **not** contain a Python. `.venv/bin/python` is normally a symlink chain ending at the base interpreter binary (`--copies` makes it a copy instead, which changes nothing about the resolution logic). The standard library is not duplicated either — `sys.path` for an environment interpreter still lists the base installation's `lib/python3.14` and `lib-dynload` directories. The only entry that differs is site-packages, plus the base site-packages when the environment opts into it. That is why an empty environment is a couple of megabytes rather than a hundred, and why the environment is welded to one interpreter minor version: the site-packages path itself carries `python3.14` in it, and the compiled extension modules inside were built for that ABI. ### Detecting an environment in code The correct check is `sys.prefix != sys.base_prefix`. It is true exactly when the interpreter concluded it is in an environment, regardless of how it was launched. The `VIRTUAL_ENV` environment variable is *not* a substitute: it is set only by the activation script, so it is absent when you invoke `.venv/bin/python` directly — which is the normal case in CI and under a service manager — and it can be left behind pointing at an environment you are not using. Older third-party environment tools set an extra attribute on the `sys` module for the same purpose; modern versions do not, so code that still probes for it is checking a fossil. For the concrete directories, `sysconfig.get_paths()` gives you the environment's `purelib` and `platlib`, and `site.getsitepackages()` lists what site added. Printing `sys.executable`, `sys.prefix` and `sys.base_prefix` is the three-line diagnostic worth memorising: it answers "which interpreter, which environment, built from what" in one shot, and it is the first thing to run when an import resolves to a package you did not expect. ### One environment, one interpreter version The `version` key and the versioned site-packages directory together explain a rule that beginners meet as folklore: an environment belongs to exactly one interpreter minor version. Its `sys.path` names `lib/python3.14`, the compiled extension modules installed into it were built against that version's ABI, and the base installation it borrows a standard library from is the one recorded in `home`. Point the same directory at a different minor version and none of that lines up. This is why the workflow is *one environment per project, per interpreter version*, and why moving a project to a new minor version means building a new environment and reinstalling into it rather than patching the old one.
- What happens if you delete pyvenv.cfg from an environment directory?The executable stops detecting an environment. On the next start `sys.prefix` equals `sys.base_prefix`, the environment's site-packages is no longer added to `sys.path`, and imports resolve against the base installation instead — so a program that ran a minute ago fails with a missing-module error while the files are still sitting on disk. The fix is to recreate the environment (`python -m venv --clear .venv`) and reinstall, rather than to hand-write the file back.
- Does a virtual environment contain its own copy of the standard library?No. Only third-party packages are private to it. `sys.path` for an environment interpreter still points at the base installation's standard library and its compiled extension directory, located through the `home` key in `pyvenv.cfg`. That is why an empty environment is small, why it stops working if the base installation is removed, and why it is bound to one interpreter minor version — the site-packages path and the compiled extensions inside it are version- and ABI-specific.
- Is reading the VIRTUAL_ENV variable a reliable way to detect an environment?No. `VIRTUAL_ENV` is exported by the activation script only, so it is absent whenever the interpreter is invoked by path — the normal case in CI, under a service manager, and from an editor. It can also be stale, still naming an environment you are no longer running. Compare `sys.prefix` with `sys.base_prefix` instead; that reflects what the interpreter actually concluded at startup.
saying these in an interview costs you the question
- Thinks a virtual environment contains its own standard library
- Detects an environment by reading the VIRTUAL_ENV variable
- Says sys.prefix and sys.base_prefix are always equal
- Believes the environment holds a different Python build
- Cannot name a single key inside pyvenv.cfg
- Thinks pip, not the interpreter, decides which site-packages is used