Environments and dependencies: what venv does, and what pip does
Questions about virtual environments seem mundane until you are asked how the interpreter knows it is in an environment. A run answers: an environment is a folder with its own bin/python and a pyvenv.cfg file; env/bin/python sets sys.prefix to the environment without any activation; activate is a shell script that puts env/bin first in PATH and sets VIRTUAL_ENV and VIRTUAL_ENV_PROMPT; a package installed into the environment is not visible to the base interpreter; pip freeze prints a snapshot of what is installed, not a lock file; a fresh environment has two packages on 3.11 — pip and setuptools — and one, pip, since 3.12; and since 3.14 the environment's sys.prefix is set by path initialization, so python -S no longer resets it.
Full technical treatment
TL;DR
A virtual environment is a separate folder with its own interpreter and its
own place for packages. It is created by python -m venv env, and packages
are installed into it by pip. That way each project has its own versions of
its dependencies, and installing a package for one project does not break
another.
Hence the main point: an environment is the interpreter env/bin/python,
not a "switched-on mode". At the root of the environment, one level above
bin/python, lies pyvenv.cfg, and from that file the interpreter sets
sys.prefix — the directory it considers its installation and from which it
looks for packages — to the environment by itself; in the run, without any
activation. activate is for the shell: it puts env/bin first in PATH so
that the python command finds the right interpreter.
Beyond that comes what separates understanding from having read about it.
A package installed into the environment is not visible to the base
interpreter: ModuleNotFoundError. pip freeze prints a snapshot of what is
installed in requirements format: a package from PyPI as name==version, an
editable or local install as a line with a path; requirements.txt is made
from the snapshot. It installs the same versions if they are available for
that platform and Python version — but it is not a lock file and not a
byte-for-byte copy. A fresh environment has two packages on 3.11, pip and
setuptools, and one, pip, since 3.12. Since 3.13 python -m venv puts a
.gitignore into the environment, and git ignores the environment. Since 3.14
the environment's sys.prefix is set by path initialization rather than by
the site module, and running with -S no longer resets it.
- how to run a command in a terminal;
- what
importdoes and what a package is — the lesson "Modules and imports"; - what the
PATHenvironment variable is; - that the lesson's commands are for a POSIX shell (bash or zsh on Linux and macOS); the Windows variant is given next to them.
python -m venv,pyvenv.cfg,sys.prefixandsys.base_prefix;- what
activatedoes,pip install,pip freeze,requirements.txt; - what changed in environments in 3.12, 3.13 and 3.14.
What it was checked on: CPython 3.11.15 and 3.12.3 (Ubuntu builds), 3.13.7 and
3.14.7; linux/x86_64, bash; pip is the one ensurepip of each version puts
into a fresh environment. The documentation and sources were checked on
14.10.2026. By that date Python 3.15 had also been released (9 October 2026) —
the lesson was not run on it.
Base: why an environment and how it is created
A full example — commands for bash/zsh (POSIX: Linux, macOS); they can be run in order in an empty folder, and installing from PyPI needs the network:
python3 -m venv env # create an environment in the env folder
source env/bin/activate # activate it (bash/zsh)
python -m pip install requests==2.32.3 # install a package into the environment
python -m pip freeze > requirements.txt # record a snapshot of what is installed
deactivate # leave the environment
python3 -m venv env2 # create a second, new environment
env2/bin/python -m pip install -r requirements.txt # install into it from the snapshotpython -m pip rather than plain pip: that way you see which interpreter —
and so which environment — the package goes into. The last command installs
packages into the second environment without activation — by the full path to
its interpreter. On Windows the environment's scripts folder is Scripts, not
bin; activation according to the venv documentation is
env\Scripts\activate.bat in cmd.exe and env\Scripts\Activate.ps1 in
PowerShell. The lesson's runs are Linux and bash only.
A junior-level answer to "why a virtual environment": so that each project
has its own package versions, independent of the system and other projects;
it is created with python -m venv, activated with activate, packages are
installed with pip, versions are pinned in requirements.txt. That is
correct, and it is followed by questions: what an environment physically is,
whether it must be activated, what exactly activate does, and why you
should not install packages into the system Python. The mechanisms below
answer them with a run.
Mechanism 1: an environment is a folder and pyvenv.cfg
Output of bench/venv/basics.py, section 1, CPython 3.13.7; labels translated:
1) what is inside the environment folder (top level and bin/)
['.gitignore', 'bin', 'include', 'lib', 'lib64', 'pyvenv.cfg']
bin/: ['Activate.ps1', 'pip', 'pip3', 'pip3.13', 'python', 'python3', 'python3.13'] + activate* scripts
bin/python is a symbolic link: True -> <python>/bin/python3.13
the environment's absolute path is written in bin/activate: True
first line of bin/pip: #!<folder>/env/bin/python3.13
pyvenv.cfg:
home = <python>/bin
include-system-site-packages = false
version = 3.13.7
There is no copy of the standard library inside the environment: bin/python
is a link to the base interpreter, lib/…/site-packages is the environment's
own folder for packages (a fresh environment has only pip there), lib64 is a
link to lib, and pyvenv.cfg records where the interpreter comes from
(home) and whether system packages are visible
(include-system-site-packages = false — they are not).
What the environment does record are absolute paths: in bin/activate and in
the first line of bin/pip — the line the system uses to choose which
interpreter runs the script. A copy of the environment in another folder on
the same machine shows what that means. Output of bench/venv/basics.py,
section 6, CPython 3.13.7; labels translated, error texts verbatim:
6) a copy of the environment in another folder on the same machine
moved/bin/python: sys.prefix -> <folder>/moved
moved/bin/pip --version -> <folder>/env/lib/python3.13/site-packages/pip
moved/bin/python -m pip --version -> <folder>/moved/lib/python3.13/site-packages/pip
old folder gone: moved/bin/pip -> FileNotFoundError: No such file or directory
The copy's interpreter sees the new folder, while bin/pip, through its
shebang, starts the old environment's interpreter — and would install packages
there. Without the old folder it does not start at all. python -m pip works
with the copy, but the other scripts in bin/ are broken the same way as
pip. On another machine, in addition, home and the bin/python link point
to a Python installation that may not exist there. That is why an environment
is not moved by copying: it is recreated and the dependencies are installed
again.
Mechanism 2: how the interpreter knows it is in an environment
If a file named "pyvenv.cfg" exists one directory above sys.executable,
sys.prefix and sys.exec_prefix are set to that directory and it is also
checked for site-packages (sys.base_prefix and sys.base_exec_prefix will
always be the "real" prefixes of the Python installation).
The environment's interpreter is run by its path, without activation. Output
of bench/venv/basics.py, section 2, CPython 3.13.7; labels translated:
2) the environment's interpreter, without activation
sys.prefix -> <folder>/env
sys.base_prefix -> <python>
prefix != base_prefix -> True
where packages go -> <folder>/env/lib/python3.13/site-packages
sys.prefix points to the environment, sys.base_prefix to the base
installation, and packages are looked up and installed in the environment's
site-packages. All of this is decided by the interpreter itself from the
pyvenv.cfg file one level above it — at the root of the environment. Hence a practical consequence: in cron, a
Dockerfile or systemd the environment is not activated — you write the full
path env/bin/python script.py. The venv documentation says the same:
You don’t specifically need to activate a virtual environment, as you can just specify the full path to that environment’s Python interpreter when invoking Python
.
And the check "am I in an environment" from code is
sys.prefix != sys.base_prefix.
Exactly what sets sys.prefix depends on the version: up to and including
3.13 it is the site module at interpreter startup, since 3.14 it is path
initialization, before site. A normal start is not affected — section 2 of
the run is identical on 3.11–3.14. The difference shows only with the -S
flag, which turns site off — more in the "Deeper" section.
Mechanism 3: package isolation and pip freeze
Output of bench/venv/basics.py, sections 3 and 4, CPython 3.13.7; labels
translated, error texts verbatim:
3) pip list in a fresh environment
pip==25.2
4) isolation: pip install tomli-w==1.0.0 into the environment
environment interpreter -> imported, version 1.0.0
base interpreter -> ModuleNotFoundError: No module named 'tomli_w'
base sys.prefix -> <python>
base packages in -> <python>/lib/python3.13/site-packages
Name in package metadata -> tomli_w
pip freeze -> ['tomli_w==1.0.0']
A fresh environment on 3.13 contains only pip. A package installed with
pip install into the environment is imported by the environment's
interpreter but not by the base one: the packages live in different
site-packages — the environment's under <folder>/env/lib/…, the base
interpreter's its own. That is isolation: projects with different versions of one
library live on one machine without disturbing each other or the system.
pip freeze prints a snapshot of what is installed — in requirements format,
as the command's description says:
Output installed packages in requirements format
. A package from PyPI
goes into the snapshot as name==version, but not every line looks like
that. In section 7 of the run a package from a local folder and an editable
package (pip install -e — an install that links to the folder with the
sources) are also installed into the environment. Output of
bench/venv/basics.py, section 7, CPython 3.13.7; labels translated, pip's
lines verbatim:
7) pip freeze: what goes into the snapshot
python -m pip freeze:
# Editable install with no version control (editpkg==0.1)
-e <folder>/editpkg
localpkg @ file://<folder>/localpkg
tomli_w==1.0.0
only in python -m pip freeze --all: ['pip==25.2']
The package from a local folder is written as a direct reference
(name @ file://…), the editable package as a -e line with a path; these
paths point to folders on this machine. freeze skips pip itself, and
--all shows it. What to skip is decided by pip from the Python version: up
to and including 3.11 — pip, setuptools, distribute and wheel, since
3.12 — only pip (pip/_internal/commands/freeze.py, lines 12–20 in pip 24.0
from 3.11.15). In the run on 3.11 --all added pip==24.0 and
setuptools==79.0.1.
The freeze output is what is saved in requirements.txt, so that
python -m pip install -r requirements.txt in a new environment installs the
same versions. That works if those versions are available for that platform
and Python version; the snapshot does not promise a byte-for-byte identical
build — it stores names, versions and paths, not the package files
themselves. pip itself separates freeze from a lock file:
pip freeze reports what is installed; it does not compute a lockfile or a solver result
.
freeze prints the name as it is written in the package's metadata —
Name: tomli_w there — not as it was typed in pip install tomli-w.
Mechanism 4: what activate does
Output of bench/venv/basics.py, section 5, CPython 3.13.7, bash on Linux;
labels translated:
5) activation in bash: what it changes
VIRTUAL_ENV=<folder>/env
VIRTUAL_ENV_PROMPT=env
first element of PATH: <folder>/env/bin
python in PATH: <folder>/env/bin/python
PATH restored after deactivate: yes
activate is a shell script, not part of Python. For which python gets
found, only PATH matters: env/bin goes first in it, and the python and
pip commands find the environment's interpreter. Besides that, activate
sets VIRTUAL_ENV and VIRTUAL_ENV_PROMPT, adds the environment's name to
the start of the shell prompt (PS1) and unsets PYTHONHOME if it is set; deactivate
undoes all of it. Inside the interpreter activation changes nothing: when
PYTHONHOME is not set, the environment's interpreter works the same whether
it is started through PATH or by its full path.
This is the POSIX variant for bash/zsh; fish and csh have their own scripts in
bin/ (activate.fish, activate.csh), and on Windows, according to the venv
documentation, it is env\Scripts\activate.bat in cmd.exe and
env\Scripts\Activate.ps1 in PowerShell. The lesson's run checked bash on
Linux only.
Deeper: what changed in environments
Comparing the runs on four versions shows four changes:
- 3.12: no
setuptoolsin a fresh environment. On 3.11pip listin a new environment givespipandsetuptools, since 3.12 onlypip. A fresh environment is filled byensurepip, and the set is defined there: on 3.11_PACKAGE_NAMES = ('setuptools', 'pip'), on 3.12('pip',). A project that implicitly relied onsetuptoolsin the environment has to declare it as a dependency explicitly on 3.12. - 3.13:
python -m venvputs a.gitignoreinto the environment. Its contentscause the entire environment directory to be ignored by git
— the environment will not end up in the repository even if it was not added to the project's.gitignore. The file is created by thepython -m venvcommand, the--without-scm-ignore-filesflag turns it off, andvenv.create()called from code does not create it by default. - 3.14: the environment's
sys.prefixis set by path initialization, not by thesitemodule, and the-Sflag no longer resets it. Details below, after the list. - 3.14:
bin/has one more name for the interpreter —𝜋thon. It is added only on 3.14 and only when the file system encoding is UTF-8; it does not affect how the environment works.
The pip version in a fresh environment is specific to each interpreter: in
the runs it is 24.0 on 3.11 and 3.12, 25.2 on 3.13, 26.2.1 on 3.14. It is
upgraded inside the environment: python -m pip install --upgrade pip — or
right at creation: python -m venv --upgrade-deps env. That flag is what the
CORE_VENV_DEPS list in the venv sources is for: pip and setuptools on
3.11, pip since 3.12.
Now the prefix on 3.14. The site documentation for 3.14:
site is no longer responsible for updating sys.prefix and sys.exec_prefix on Virtual Environments. This is now done during the path initialization
— and so the environment's prefixes no longer depend on -S, the flag that
starts the interpreter without the site module. Section 8 of the run is the
same env/bin/python, but with -S. Output of bench/venv/basics.py,
section 8, CPython 3.13.7; labels translated:
8) env/bin/python -S: the site module is not imported
sys.prefix -> <python>
sys.base_prefix -> <python>
prefix != base_prefix -> False
site in sys.modules -> False
site-packages in sys.path -> False
On 3.11 and 3.12 it is the same: the environment's prefix was set by site,
and without it the environment's interpreter considered itself the base
installation. The same section on CPython 3.14.7:
8) env/bin/python -S: the site module is not imported
sys.prefix -> <folder>/env
sys.base_prefix -> <python>
prefix != base_prefix -> True
site in sys.modules -> False
site-packages in sys.path -> False
Setting the prefix and adding site-packages are now separate: the prefix is
set by path initialization, while the environment's site-packages is still
added to sys.path by site, and with -S — the last line — it is not in
sys.path on any version. Without -S there is no difference between
versions: section 2 of the run is identical on 3.11–3.14, and the earlier
explanation "the interpreter finds the environment by itself from
pyvenv.cfg" stays correct — in 3.14 only the part of the interpreter that
does it changes.
How to answer in an interview
Short answer: a virtual environment is a folder with its own interpreter and
its own site-packages; it is created with python -m venv. The
environment's interpreter finds it by itself from pyvenv.cfg, and activate
is for the shell — through PATH the python command finds the
environment's interpreter. Packages are installed with python -m pip inside
the environment, the installed versions are recorded with pip freeze in
requirements.txt — a snapshot of what is installed, not a lock file.
Packages are not installed into the system Python: an environment isolates
the versions of projects.
That is enough for a correct answer. What follows is what you add if the interviewer digs.
If the interviewer digs deeper
On "do you have to activate an environment". "No. The interpreter
env/bin/python sets sys.prefix to the environment by itself from
pyvenv.cfg. In my run, without activation sys.prefix != sys.base_prefix
was True. In Docker and cron that is how it is run — by the full path."
On "what does activate do". "Mainly it puts env/bin first in PATH, so
python and pip find the environment. It also sets VIRTUAL_ENV and
VIRTUAL_ENV_PROMPT, changes the shell prompt and unsets PYTHONHOME;
deactivate undoes it all. It does not change Python itself — only the shell
variables Python inherits."
On "can you copy an environment to another machine". "No. bin/activate
and the first line of bin/pip record the environment's absolute path. In my
run, even in another folder the copy's bin/pip started the old environment,
and without the old folder it did not start at all. On
another machine home in pyvenv.cfg and the bin/python link also point to
a Python installation that may not exist there. You move the dependency list — requirements.txt or a lock file — and
create the environment anew, on a compatible platform and Python version."
On "how does pip freeze differ from a hand-written requirements.txt".
"pip freeze prints a snapshot of everything installed in requirements
format, with exact versions, including transitive dependencies — except pip
itself (on Python up to 3.11 also setuptools, wheel and distribute); it
writes editable and local installs as lines with a path. By hand people
usually list only direct dependencies. And it is not a lock file: pip states
plainly that freeze reports what is installed and does not compute a lock
file."
Next they ask
How does venv differ from poetry and uv?
venv only creates an environment. pip installs packages and resolves
compatible versions while installing, but it does not keep the project's
dependency list and does not write a lock file by default. Poetry and uv do:
they keep the dependencies in pyproject.toml, record the exact versions of
the whole tree in a lock file, and create the environment themselves. Underneath it is the same environment
with pyvenv.cfg.
What is --system-site-packages for?
It writes include-system-site-packages = true into pyvenv.cfg, and the
environment sees the packages of the base installation — heavy system
libraries, for example, that you do not want to install again. Isolation is
weaker then: in the lesson's run this key is false, and base packages are
not visible.
Why is installing packages into the system Python bad?
On Linux distributions the system Python is used by the operating system
itself, and its tools rely
on specific package versions. Installing on top may break them, and two
projects with different versions of one library cannot coexist in one
site-packages. An environment solves both problems.
Common misconceptions
An environment does not work without activate
The interpreter env/bin/python finds the environment by itself from pyvenv.cfg: in the run, without activation sys.prefix != sys.base_prefix was True.
An environment contains a copy of Python
With python -m venv on Linux and macOS bin/python is a symbolic link to the base interpreter, and home in pyvenv.cfg points to its folder. With the --copies flag the executable itself is copied, and on Windows the environment gets a small launcher that starts the base python.exe. In no case is there a standard library inside: it comes from the base installation.
activate changes something inside Python
It does not touch the interpreter itself: it changes shell variables — PATH, VIRTUAL_ENV, VIRTUAL_ENV_PROMPT, the prompt. It unsets PYTHONHOME so that a set variable does not lead the environment's interpreter into another installation. After deactivate PATH is as before.
A new environment always has setuptools
Only up to and including 3.11. Since 3.12 a fresh environment has just pip.
A requirements.txt from pip freeze reproduces the environment on any machine
It is a snapshot of what is installed: names, versions, and for editable and local installs, paths on this machine. The same versions are installed if they are available for that platform and Python version; the snapshot does not promise a byte-for-byte copy, and, in pip's own words, it is not a lock file.
An environment can be copied to another folder or machine
bin/activate and the first line of bin/pip hold the environment's absolute path: in the run the copy's bin/pip started the old environment, and without the old folder it did not start. On another machine the Python installation that home points to may also be missing. An environment is recreated from requirements.txt.
Version history
| Version | Change | What it means for code |
|---|---|---|
| 3.11 | The reference point of the lesson. A fresh environment has pip and setuptools. | |
| 3.12 | venv no longer installs setuptools: a fresh environment has just pip. A build that implicitly relied on setuptools has to declare it explicitly. | |
| 3.13 | The python -m venv command creates a .gitignore with an asterisk in the environment folder — git does not see the environment. | |
| 3.14 | The environment's sys.prefix is set by path initialization, not by the site module: in the run env/bin/python -S on 3.14.7 sees the environment (prefix != base_prefix is True), on 3.11–3.13 it does not. Without -S everything is as before. Also bin/ has one more name for the interpreter, 𝜋thon, only when the file system encoding is UTF-8; it does not affect how the environment works. | |
| 3.15 | Released on 9 October 2026. The lesson was not run on it: the runs are CPython 3.11–3.14, checked on 14.10.2026. |
Practice
Two problems. Answer first, then check against the real output: in both, the correct answer is taken from a run of the script rather than assigned.
Practice · predict the output
import sys
print(sys.prefix == sys.base_prefix)
print(sys.prefix.endswith("env"))Practice · estimate
Check yourself
A cron script runs as /app/env/bin/python job.py; activate is not called. Which packages will it see?
How it was measured
The observations of this lesson come from these scripts. Each one opens right from here — together with the record of its run: what it ran on and what came out.
This is neither a retelling nor a separate text: everything below is taken from the article itself — its own summary, the section headings, the “actually” column and the version table. Which is why these theses cannot drift from the article.
The gist
- A virtual environment is a separate folder with its own interpreter and its own place for packages. It is created by
python -m venv env, and packages are installed into it bypip. That way each project has its own versions of its dependencies, and installing a package for one project does not break another. - Hence the main point: an environment is the interpreter
env/bin/python, not a "switched-on mode". At the root of the environment, one level abovebin/python, liespyvenv.cfg, and from that file the interpreter setssys.prefix— the directory it considers its installation and from which it looks for packages — to the environment by itself; in the run, without any activation.activateis for the shell: it putsenv/binfirst inPATHso that thepythoncommand finds the right interpreter. - Beyond that comes what separates understanding from having read about it. A package installed into the environment is not visible to the base interpreter:
ModuleNotFoundError.pip freezeprints a snapshot of what is installed in requirements format: a package from PyPI asname==version, an editable or local install as a line with a path;requirements.txtis made from the snapshot. It installs the same versions if they are available for that platform and Python version — but it is not a lock file and not a byte-for-byte copy. A fresh environment has two packages on 3.11,pipandsetuptools, and one,pip, since 3.12. Since 3.13python -m venvputs a.gitignoreinto the environment, and git ignores the environment. Since 3.14 the environment'ssys.prefixis set by path initialization rather than by thesitemodule, and running with-Sno longer resets it.
In fact
- The interpreter
env/bin/pythonfinds the environment by itself frompyvenv.cfg: in the run, without activationsys.prefix != sys.base_prefixwasTrue. - With
python -m venvon Linux and macOSbin/pythonis a symbolic link to the base interpreter, andhomeinpyvenv.cfgpoints to its folder. With the--copiesflag the executable itself is copied, and on Windows the environment gets a small launcher that starts the basepython.exe. In no case is there a standard library inside: it comes from the base installation. - It does not touch the interpreter itself: it changes shell variables —
PATH,VIRTUAL_ENV,VIRTUAL_ENV_PROMPT, the prompt. It unsetsPYTHONHOMEso that a set variable does not lead the environment's interpreter into another installation. AfterdeactivatePATHis as before. - Only up to and including 3.11. Since 3.12 a fresh environment has just
pip. - It is a snapshot of what is installed: names, versions, and for editable and local installs, paths on this machine. The same versions are installed if they are available for that platform and Python version; the snapshot does not promise a byte-for-byte copy, and, in pip's own words, it is not a lock file.
bin/activateand the first line ofbin/piphold the environment's absolute path: in the run the copy'sbin/pipstarted the old environment, and without the old folder it did not start. On another machine the Python installation thathomepoints to may also be missing. An environment is recreated fromrequirements.txt.
By version
- 3.11
- The reference point of the lesson. A fresh environment has
pipandsetuptools.< - 3.12
venvno longer installssetuptools: a fresh environment has justpip. A build that implicitly relied onsetuptoolshas to declare it explicitly.<- 3.13
- The
python -m venvcommand creates a.gitignorewith an asterisk in the environment folder — git does not see the environment.< - 3.14
- The environment's
sys.prefixis set by path initialization, not by thesitemodule: in the runenv/bin/python -Son 3.14.7 sees the environment (prefix != base_prefixisTrue), on 3.11–3.13 it does not. Without-Severything is as before. Alsobin/has one more name for the interpreter,𝜋thon, only when the file system encoding is UTF-8; it does not affect how the environment works.< - 3.15
- Released on 9 October 2026. The lesson was not run on it: the runs are CPython 3.11–3.14, checked on 14.10.2026.<
What is covered
- Base: why an environment and how it is created
- Mechanism 1: an environment is a folder and pyvenv.cfg
- Mechanism 2: how the interpreter knows it is in an environment
- Mechanism 3: package isolation and pip freeze
- Mechanism 4: what activate does
- Deeper: what changed in environments
- How to answer in an interview
- Next they ask
- Common misconceptions
- Version history
- Practice
- Check yourself
- How it was measured
Sources & further reading
7 SOURCES
- The site module — how the interpreter finds an environmentOfficial documentation. The docstring of the site module: «If a file named "pyvenv.cfg" exists one directory above sys.executable, sys.prefix and sys.exec_prefix are set to that directory and it is also checked for site-packages (sys.base_prefix and sys.base_exec_prefix will always be the "real" prefixes of the Python installation)». Checked by calling site.__doc__ on 3.11.15, 3.12.3, 3.13.7 and 3.14.7.https://docs.python.org/3.13/library/site.html
- Lib/venv/__init__.py — what venv puts into an environmentCPython source code. On 3.13.7: lines 444–446 are the _setup_pip method, which fills a fresh environment by calling ensurepip; line 17 is
CORE_VENV_DEPS, the package list for the --upgrade-deps flag (the upgrade_dependencies method, lines 585–590; on 3.11.15 —('pip', 'setuptools'), since 3.12.3 —('pip',)). Lines 279–290 are the create_git_ignore_file method: «The contents of the file cause the entire environment directory to be ignored by git»; the python -m venv command line turns it on by default (line 662,default=frozenset(['git'])), while EnvBuilder and venv.create default toscm_ignore_files=frozenset()(lines 51 and 595). Lines 185–190 create lib64 as a symbolic link to lib on 64-bit POSIX systems other than macOS. Lines 318–345: on Windows, instead of a link, the launcher venvlauncher.exe is copied into the environment as python.exe; lines 623–626: on Windows the command line turns symbolic links off by default. On 3.14.7, lines 318–319 aresuffixes.append('𝜋thon'), only whensys.version_info[:2] == (3, 14)and the file system encoding is UTF-8.https://github.com/python/cpython/blob/v3.13.7/Lib/venv/__init__.py - Lib/ensurepip/__init__.py — what goes into a fresh environmentCPython source code. On 3.11.15, lines 12–14:
_PACKAGE_NAMES = ('setuptools', 'pip'),_SETUPTOOLS_VERSION = "79.0.1",_PIP_VERSION = "24.0". On 3.12.3, lines 40–41:_PACKAGE_NAMES = ('pip',),_PIP_VERSION = "24.0". On 3.13.7 and 3.14.7 there is no_PACKAGE_NAMESor_SETUPTOOLS_VERSION, only pip is installed;_PIP_VERSION(line 13) is "25.2" and "26.2.1". Checked against the installed files of these versions; 3.11.15 and 3.12.3 are Ubuntu builds, the line numbers are given for them and may differ in the CPython source.https://github.com/python/cpython/blob/v3.12.3/Lib/ensurepip/__init__.py - Lib/venv/scripts/common/activate — what activation doesCPython source code. The activate template on 3.13.7: lines 57–58 —
export VIRTUAL_ENV_PROMPT; 60–66 — unsets PYTHONHOME if it is set; 69–71 — adds the environment's name to the start of PS1 unless VIRTUAL_ENV_DISABLE_PROMPT is set; 76 —hash -r; lines 4–34 — the deactivate function, which undoes all of it.https://github.com/python/cpython/blob/v3.13.7/Lib/venv/scripts/common/activate - The site module in 3.14 — path initialization sets the environment's prefixesOfficial documentation. The site documentation for 3.14: «Changed in version 3.14: site is no longer responsible for updating sys.prefix and sys.exec_prefix on Virtual Environments. This is now done during the path initialization. As a result, under Virtual Environments, sys.prefix and sys.exec_prefix no longer depend on the site initialization, and are therefore unaffected by -S». In the source: the venv() function in Lib/site.py on 3.13.7, line 628, is
sys.prefix = sys.exec_prefix = site_prefix(in the Ubuntu builds of 3.11.15 and 3.12.3, lines 554 and 555); on 3.14.7, lines 634–637, it only warns if sys.prefix does not match the environment folder. The docstring of the site module (site.__doc__) on 3.14.7 is unchanged. The run isbench/venv/basics.py, section 8. Checked on 14.10.2026.https://docs.python.org/3.14/library/site.html - pip/_internal/commands/freeze.py — what pip freeze skipsSource code. In pip 24.0, which ensurepip installs on 3.11.15: line 27 is the command description «Output installed packages in requirements format»; lines 12–20 —
_should_suppress_build_backends()returnssys.version_info < (3, 12), and thensetuptools,distributeandwheelare added to the skippedpip; since Python 3.12 onlypipis skipped. In pip 25.2 (3.13.7) and 26.2.1 (3.14.7) the rule is the same, lines 11–19. The --all flag turns the skipping off. The pip documentation on the difference from a lock file: «pip freeze reports what is installed; it does not compute a lockfile or a solver result». Checked against the installed files and pip.pypa.io on 14.10.2026.https://pip.pypa.io/en/stable/cli/pip_freeze/ - venv — activation commands on different platformsOfficial documentation. The table in the venv 3.14 documentation: POSIX, bash/zsh —
source <venv>/bin/activate; fish —source <venv>/bin/activate.fish; csh/tcsh —source <venv>/bin/activate.csh; Windows, cmd.exe —<venv>\Scripts\activate.bat; PowerShell —<venv>\Scripts\Activate.ps1. And: «You don’t specifically need to activate a virtual environment, as you can just specify the full path to that environment’s Python interpreter when invoking Python». The lesson's run is bash on Linux only. Checked on 14.10.2026.https://docs.python.org/3.14/library/venv.html