Python Module 1 – Getting started: install, run and read Python
Virtual environments with venv and pip
Create a Python virtual environment with venv, install and list packages in it with python -m pip, and see why many system Pythons refuse a pip install.
What you will learn
- Create, activate and delete a virtual environment with venv
- Install, list and remove packages with python -m pip
- Detect from a program whether it runs in a virtual environment
- Explain why many system Pythons refuse pip install
- Rebuild an environment from requirements.txt instead of moving it
Before you start
What you’ll learn · Before you start
What you will learn
- Create, activate and delete a virtual environment with venv
- Install, list and remove packages with python -m pip
- Detect from a program whether it runs in a virtual environment
- Explain why many system Pythons refuse pip install
- Rebuild an environment from requirements.txt instead of moving it
Before you start
On this page
Two projects on one computer can need two versions of the same package, one written for version 1 of a library and
one for version 2. With a single Python, one pip install serves both, so an upgrade for one project can break the
other, and the Python that came with your operating system may also run tools of the system itself. A virtual
environment gives each project a folder of packages of its own.
Below you create an environment and look inside it, see what activating it changes, install a package into it without
a download, see why many system Pythons refuse a plain pip install, and rebuild an environment instead of moving it.
Every command ran with this track’s Python 3.14.8, and the page shows exactly what it printed.
A virtual environment is a folder with its own packages
A virtual environment is a folder, by convention named .venv and kept in the project’s own folder. Three things in
it matter:
pyvenv.cfg, a small text file that names the Python the environment was made from, its base Python;bin/python(Scripts\python.exeon Windows), the environment’s ownpythoncommand;lib/python3.14/site-packages(Lib\site-packageson Windows), where the packages you install for this project go.
What is inside a virtual environment
Text description of the diagram
The diagram shows, from top to bottom, a project folder and, below it and outside it, the base Python.
- The project folder holds your own code, main.py, and the virtual environment, a folder named .venv.
- main.py runs with the environment's own python command, bin/python (Scripts\python.exe on Windows).
- That python reads pyvenv.cfg and imports packages from the environment's site-packages folder (lib/python3.14/site-packages, or Lib\site-packages on Windows), where pip installs this project's packages.
- pyvenv.cfg names the base Python 3.14 the environment was made from. The environment's python is a link to that interpreter (on Windows, a small launcher that starts it) and uses its standard library: only the packages belong to the environment.
Create one with python3 -m venv .venv in the project’s folder, or py -m venv .venv on Windows. -m venv runs the
venv module of the Python you typed, so the environment is built on that Python and keeps its version. The command
prints nothing when it works. On Debian it needs one more package first: Debian ships venv in a package of its own,
python3-venv, and Ubuntu’s documentation lists venv among what its python3-full package adds. This program
creates an environment and looks inside:
look_inside.py
import sys
from pathlib import Path
from session import run
# The environment's programs are in .venv/bin (.venv\Scripts on Windows).
run("python3 -m venv .venv", sys.executable, "-m", "venv", ".venv")
print("In .venv:", sorted(p.name for p in Path(".venv").iterdir()))
print("pyvenv.cfg says:")
print(Path(".venv/pyvenv.cfg").read_text(), end="")
packages = Path(".venv/lib/python3.14/site-packages")
print("site-packages holds:", sorted(p.name for p in packages.iterdir()))
print(".gitignore holds:", Path(".venv/.gitignore").read_text().splitlines()[-1])
check = "import sys; print(sys.prefix != sys.base_prefix)"
run(f".venv/bin/python -c '{check}'", ".venv/bin/python", "-c", check)
run(f"python3 -c '{check}'", sys.executable, "-c", check) Output
$ python3 -m venv .venv In .venv: ['.gitignore', 'bin', 'include', 'lib', 'pyvenv.cfg'] pyvenv.cfg says: home = …/bin include-system-site-packages = false version = 3.14.8 executable = …/bin/python3.14 command = …/bin/python3 -m venv .venv site-packages holds: ['pip', 'pip-26.2.1.dist-info'] .gitignore holds: * $ .venv/bin/python -c 'import sys; print(sys.prefix != sys.base_prefix)' True $ python3 -c 'import sys; print(sys.prefix != sys.base_prefix)' False
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 look_inside.py
session.py
"""Runs a command as if you had typed it in a terminal, and prints what it printed."""
import os
import subprocess
# PIP_DISABLE_PIP_VERSION_CHECK: pip does not look online for a newer pip,
# so what these examples print does not depend on the network.
ENV = {**os.environ, "PIP_DISABLE_PIP_VERSION_CHECK": "1"}
def run(typed, *command):
"""Show the command as you would type it, run it, and print its output."""
print("$", typed)
result = subprocess.run(command, capture_output=True, text=True, env=ENV)
print(result.stdout + result.stderr, end="")
return result Four things to see in that output. The standard library was not copied: site-packages holds only pip, the one
package a new environment starts with, in the version this Python bundles, 26.2.1. pyvenv.cfg names the base
Python’s folder in home (the page shortens that folder to …), and include-system-site-packages = false keeps
the base Python’s own packages out. venv wrote a .gitignore holding *, so Git ignores the whole folder, which
venv has done since Python 3.13. And sys.prefix != sys.base_prefix is True inside the environment and False
outside: the venv documentation gives this comparison as the way a program can tell.
How does .venv/bin/python know that it belongs to an environment? Step through one start and one import:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- You start .venv/bin/python. On macOS and Linux, .venv/bin/python is usually a link to the base Python's interpreter (on Windows, a small launcher that starts it), so the base interpreter is what runs.
- It finds pyvenv.cfg. At start-up Python looks for pyvenv.cfg next to its program and one folder up. It finds .venv/pyvenv.cfg, so sys.prefix becomes .venv while sys.base_prefix stays the base Python.
- The standard library is shared. json, pathlib and the rest of the standard library still come from the base Python, which is why making an environment takes seconds and copies none of them.
- Packages come from .venv. The environment's own site-packages folder is searched for packages, so whatever pip installed into this environment can be imported.
- Base packages stay out. pyvenv.cfg says include-system-site-packages = false, so the base Python's own site-packages folder is not searched. A package installed there is invisible here.
- One project, one set of packages. Another project's .venv has its own site-packages and the same base Python. The two share the interpreter and the standard library, and never each other's packages.
Key idea
A virtual environment is a folder of packages with a pyvenv.cfg that points back to a base Python: the interpreter
and the standard library are the base Python’s, and only the packages belong to the environment.
Checkpoint
Check: What a virtual environment is
Activating puts the environment’s python first on PATH
Typing .venv/bin/python every time is long. Activating the environment puts its bin folder (Scripts on
Windows) first on your PATH: The environment variable that lists the folders a shell searches, in order, for a command you type. A command whose folder is not on PATH answers "command not found" until the folder is added or the full path is typed., so that python, python3 and pip mean the
environment’s until you type deactivate or close the terminal. Here is a bash shell before, during and after:
import subprocess
import sys
from session import run
run("python3 -m venv .venv", sys.executable, "-m", "venv", ".venv")
# The commands below run in one bash shell, one after another, as if typed.
commands = [
"command -v python3",
"source .venv/bin/activate",
"command -v python3",
'echo "$PS1"',
"deactivate",
"command -v python3",
]
script = "".join(f"echo '$ {line}'; {line}\n" for line in commands)
shell = subprocess.run(["bash", "-c", script], capture_output=True, text=True)
print(shell.stdout + shell.stderr, end="") Output
$ python3 -m venv .venv $ command -v python3 …/bin/python3 $ source .venv/bin/activate $ command -v python3 .venv/bin/python3 $ echo "$PS1" (.venv) $ deactivate $ command -v python3 …/bin/python3
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 activate_session.py
Before activation, python3 was the base Python, this track’s (shortened to …). After source .venv/bin/activate
the shell found .venv/bin/python3 first, and the prompt started with (.venv), which is how you can see that an
environment is active. deactivate put both back. The command to activate depends on your shell:
| Shell | Activate with |
|---|---|
| bash or zsh (macOS, Linux) | source .venv/bin/activate |
| fish | source .venv/bin/activate.fish |
| csh or tcsh | source .venv/bin/activate.csh |
| PowerShell (Windows) | .venv\Scripts\Activate.ps1 |
| Command Prompt (Windows) | .venv\Scripts\activate.bat |
If PowerShell refuses to run Activate.ps1, the venv documentation gives the command that allows such scripts for
your own user: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser.
Activation is a convenience, not a requirement. The full path of the environment’s interpreter runs a program in the
environment with nothing activated: .venv/bin/python main.py, or .venv\Scripts\python main.py on Windows. That is
how editors and scheduled jobs use an environment.
Checkpoint
Check: Activating an environment
pip installs into the environment whose python runs it
python -m pip runs the pip of that python, so it installs into that python’s environment. The program below first
builds a small package file, a wheel: A built package, a zip file with the extension .whl, often one per platform. pip installs a wheel by unpacking its files, without running the package's own code. More in “Finding and installing third-party packages” made by make_wheel.py, so that the install needs no
download: --no-index --find-links wheels tells pip to look only in that folder. Installing from
PyPI: The Python Package Index, pypi.org, where most third-party Python packages are published. pip installs from it unless it is told to use another index or a folder of files. More in “Finding and installing third-party packages” works the same way, with a download:
install_session.py
import sys
from make_wheel import make_wheel
from session import run
make_wheel("wheels") # a package file to install from, so no download is needed
py = ".venv/bin/python"
run("python3 -m venv .venv", sys.executable, "-m", "venv", ".venv")
run(f"{py} -m pip --version", py, "-m", "pip", "--version")
run(
f"{py} -m pip install --no-index --find-links wheels marksheet",
py, "-m", "pip", "install", "--no-index", "--find-links", "wheels", "marksheet",
)
run(f"{py} -m pip list", py, "-m", "pip", "list")
where = "import marksheet; print(marksheet.__file__)"
run(f"{py} -c '{where}'", py, "-c", where)
run("python3 -c 'import marksheet'", sys.executable, "-c", "import marksheet")
run(f"{py} -m pip uninstall -y marksheet", py, "-m", "pip", "uninstall", "-y", "marksheet") Output
$ python3 -m venv .venv
$ .venv/bin/python -m pip --version
pip 26.2.1 from .venv/lib/python3.14/site-packages/pip (python 3.14)
$ .venv/bin/python -m pip install --no-index --find-links wheels marksheet
Looking in links: wheels
Processing ./wheels/marksheet-1.0.0-py3-none-any.whl
Installing collected packages: marksheet
Successfully installed marksheet-1.0.0
$ .venv/bin/python -m pip list
Package Version
--------- -------
marksheet 1.0.0
pip 26.2.1
$ .venv/bin/python -c 'import marksheet; print(marksheet.__file__)'
.venv/lib/python3.14/site-packages/marksheet.py
$ python3 -c 'import marksheet'
Traceback (most recent call last):
File "<string>", line 1, in <module>
import marksheet
ModuleNotFoundError: No module named 'marksheet'
$ .venv/bin/python -m pip uninstall -y marksheet
Found existing installation: marksheet 1.0.0
Uninstalling marksheet-1.0.0:
Successfully uninstalled marksheet-1.0.0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 install_session.py
make_wheel.py
"""Builds marksheet-1.0.0-py3-none-any.whl, a tiny package, so that no download is needed.
A wheel is a zip file: the package's code, and a NAME-VERSION.dist-info folder that
says what the package is and which commands it adds.
"""
import base64
import hashlib
import zipfile
from pathlib import Path
CODE = '''def grade(mark):
return "pass" if mark >= 35 else "fail"
def main():
print("72 is a", grade(72))
'''
def make_wheel(folder):
info = "marksheet-1.0.0.dist-info"
files = {
"marksheet.py": CODE,
f"{info}/METADATA": "Metadata-Version: 2.4\nName: marksheet\nVersion: 1.0.0\n",
f"{info}/WHEEL": "Wheel-Version: 1.0\nGenerator: make_wheel.py\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
f"{info}/entry_points.txt": "[console_scripts]\nmarksheet = marksheet:main\n",
}
record = []
for name, text in files.items():
digest = base64.urlsafe_b64encode(hashlib.sha256(text.encode()).digest())
record.append(f"{name},sha256={digest.rstrip(b'=').decode()},{len(text.encode())}")
files[f"{info}/RECORD"] = "\n".join(record + [f"{info}/RECORD,,"]) + "\n"
Path(folder).mkdir(exist_ok=True)
wheel = Path(folder, "marksheet-1.0.0-py3-none-any.whl")
with zipfile.ZipFile(wheel, "w") as archive:
for name, text in files.items():
archive.writestr(name, text)
return wheel pip --version names the folder its pip runs from, the environment’s site-packages, and the package landed there
too, as .venv/lib/python3.14/site-packages/marksheet.py. So the environment’s python imports it, and the base
python3 stops with ModuleNotFoundError: that isolation is what a virtual environment is for. With the
environment active, plain python -m pip means the same:
| Task | Command |
|---|---|
| Install a package | python -m pip install requests |
| Install a version range | python -m pip install "requests>=2,<3" |
| Upgrade a package | python -m pip install --upgrade requests |
| List what is installed | python -m pip list |
| Remove a package | python -m pip uninstall requests |
| Save the exact versions | python -m pip freeze > requirements.txt |
| Install from that file | python -m pip install -r requirements.txt |
Quote a version range, as in the second row: without the quotes, the shell reads < and > as redirections. As
the REPL lesson showed, python -m pip also makes sure that the pip of the right
interpreter runs, where a bare pip may belong to another Python on your PATH.
Security
A package you install runs its code on your computer whenever your program uses it. Check the spelling of the name and the project’s page on pypi.org before you install: with typosquatting: Registering a package name one slip away from a popular one, such as a letter dropped, two letters swapped or a separator removed, so that a typo installs someone else's code., a name one slip away from a popular package, a typo installs someone else’s code.
Checkpoint
Check: Installing into an environment
Many system Pythons refuse pip install outside an environment
Run python3 -m pip install requests with the Python of Debian 12 or later, or of Homebrew on a Mac, outside any
environment, and pip stops with error: externally-managed-environment and
This environment is externally managed. That Python belongs to the system’s package manager, which installs its
packages itself; a pip install could replace a package that a tool of the system needs. The rule is a packaging
standard, from PEP 668. Step through the check that pip 26.2.1 makes before it installs:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Outside any environment. You run python3 -m pip install requests with the Python of Debian 12 or of Homebrew, with no virtual environment active.
- First question. pip asks whether it runs inside a virtual environment, by comparing sys.prefix with sys.base_prefix. Here they are the same folder, so the answer is no.
- Second question. It looks for a file named EXTERNALLY-MANAGED in the standard library's folder. The system's package manager put one there, to say that it installs this Python's packages itself.
- pip refuses. pip stops with error: externally-managed-environment and shows the message from that file, which usually says to make a virtual environment. Nothing was installed or changed.
- Inside a virtual environment. Run the same command with .venv/bin/python and the first answer is yes. pip installs into the environment and never looks for the file, which is why an environment always works.
- A Python without the file. A Python with no such file, such as this track's from mise, lets pip install, but into the one base Python that every project shares. An environment is still the better place.
A program can make the same check that pip makes. This track’s Python 3.14.8 was installed by a version manager,
mise, not by a system’s package manager, so every answer is False: nothing marks it as managed.
import sys
import sysconfig
from pathlib import Path
in_venv = sys.prefix != sys.base_prefix
marker = Path(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED")
print("Python", sys.version.split()[0])
print("Inside a virtual environment:", in_venv)
print("Marked as externally managed:", marker.exists())
print("A plain pip install would be refused:", marker.exists() and not in_venv) Output
Python 3.14.8 Inside a virtual environment: False Marked as externally managed: False A plain pip install would be refused: False
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 managed.py
Run the same file with the python3 of Debian 12 or Homebrew and the third line says True, and so does the
fourth, unless you run it from a virtual environment, where pip is always allowed to install. When you meet the error:
- For anything your own code imports, make a virtual environment, as in this lesson.
- For a command-line application you want everywhere, install it with pipx, the next lesson’s tool, which gives each application an environment of its own.
- For a library the system packages itself, install the system’s package with its package manager, as the
message of Debian’s Python suggests (
apt install python3-xyz).
Warning
Do not use sudo pip install, and keep away from pip’s --break-system-packages option, which, as its
documentation says, allows pip to modify an externally managed installation. Both change files that the operating
system’s own tools depend on.
Checkpoint
Check: System Pythons that refuse pip
Delete and rebuild an environment, never move it
Nothing of yours lives in .venv, so deleting it is safe: remove the folder (rm -rf .venv on macOS and Linux,
Remove-Item -Recurse .venv in PowerShell, or with your file manager). Before you do, record the exact versions in a
requirements file: A text file, usually requirements.txt, with one requirement per line, which pip install -r installs. pip freeze writes one that lists every installed package with its exact version. More in “Finding and installing third-party packages” with pip freeze, so that a new environment can get the same
packages again.
Moving is what breaks. Every command that pip installs, such as marksheet below, is a small script whose first line,
#!, holds the full path of the environment’s python, so after a move that path no longer exists. The program shows
the whole cycle, from freezing to rebuilding:
import os
import shutil
import subprocess
import sys
from pathlib import Path
from make_wheel import make_wheel
from session import run
def first_line(script):
"""Where the #! line of a script that pip installed points, and whether that python exists."""
python = script.read_text().splitlines()[0].removeprefix("#!")
print(f" {script} starts with #! and the full path of {os.path.relpath(python)};",
"it exists:", Path(python).exists())
make_wheel("wheels")
py = ".venv/bin/python"
find = ["--no-index", "--find-links", "wheels"]
run("python3 -m venv .venv", sys.executable, "-m", "venv", ".venv")
run(f"{py} -m pip install --no-index --find-links wheels marksheet", py, "-m", "pip", "install", *find, "marksheet")
frozen = run(f"{py} -m pip freeze", py, "-m", "pip", "freeze").stdout
Path("requirements.txt").write_text(frozen) # what pip freeze > requirements.txt does
first_line(Path(".venv/bin/marksheet"))
print("$ mv .venv moved")
shutil.move(".venv", "moved")
first_line(Path("moved/bin/marksheet"))
print("$ moved/bin/marksheet")
try:
subprocess.run(["moved/bin/marksheet"], check=True)
except FileNotFoundError as error: # the python its #! line names is gone
print(" it does not start:", error.strerror)
print("$ rm -rf moved")
shutil.rmtree("moved")
run("python3 -m venv .venv", sys.executable, "-m", "venv", ".venv")
run(f"{py} -m pip install --no-index --find-links wheels -r requirements.txt",
py, "-m", "pip", "install", *find, "-r", "requirements.txt")
run(".venv/bin/marksheet", ".venv/bin/marksheet") Output
$ python3 -m venv .venv $ .venv/bin/python -m pip install --no-index --find-links wheels marksheet Looking in links: wheels Processing ./wheels/marksheet-1.0.0-py3-none-any.whl Installing collected packages: marksheet Successfully installed marksheet-1.0.0 $ .venv/bin/python -m pip freeze marksheet==1.0.0 .venv/bin/marksheet starts with #! and the full path of .venv/bin/python; it exists: True $ mv .venv moved moved/bin/marksheet starts with #! and the full path of .venv/bin/python; it exists: False $ moved/bin/marksheet it does not start: No such file or directory $ rm -rf moved $ python3 -m venv .venv $ .venv/bin/python -m pip install --no-index --find-links wheels -r requirements.txt Looking in links: wheels Processing ./wheels/marksheet-1.0.0-py3-none-any.whl (from -r requirements.txt (line 1)) Installing collected packages: marksheet Successfully installed marksheet-1.0.0 $ .venv/bin/marksheet 72 is a pass
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 move_and_rebuild.py
The moved command still named .venv/bin/python, which was gone, so it did not start (the page writes that full
path relative to the example’s folder). The environment rebuilt from requirements.txt works again, with the same
marksheet 1.0.0. The venv documentation says the same: environments are not movable or copyable, so you recreate
them in the new place.
Committing or copying .venv
Putting .venv into Git, or copying it to another computer, ships a folder full of paths that only make sense on the
computer that made it. Commit requirements.txt (or the project’s pyproject.toml, which the next lesson
introduces) and let each person build their own environment from it.
Checkpoint
Check: Rebuilding instead of moving
Practise: check an environment
Two short functions use the ideas of this lesson: one tells whether a program runs in a virtual environment, the
other reads the versions that pip freeze recorded. Both steps come with starter code, a first hint and sample tests
you can run here in the page, so do them in either order:
Code lab · 2 steps
Check an environment
Tell whether a Python runs in a virtual environment, then read the exact versions that pip freeze recorded.
- Tell whether a Python runs in a virtual environment To do
- Read the versions that pip freeze recorded To do
Exercise · Easy · Python
Tell whether a Python runs in a virtual environment
Write in_virtualenv(sys_module). It is given an object with the attributes prefix and base_prefix, such as the sys module itself, and returns True when the Python it describes runs inside a virtual environment, False when it is a base Python.
- Inside an environment,
prefixis the environment's folder andbase_prefixthe base Python's, so they differ. - In a base Python they are the same folder.
- The tests pass fake objects as well as the real
sys, so read the attributes ofsys_module, not ofsys.
Starter code · venvcheck.py
def in_virtualenv(sys_module):
"""Return True if the Python that sys_module describes runs in a virtual environment."""
return None The sample tests · test_venvcheck.py
import sys
from types import SimpleNamespace
from venvcheck import in_virtualenv
def test_inside_an_environment():
"""a prefix that differs from base_prefix means a virtual environment"""
fake = SimpleNamespace(prefix="/home/asha/marks/.venv", base_prefix="/usr")
assert in_virtualenv(fake) is True
def test_base_python():
"""the same prefix and base_prefix means the base Python itself"""
fake = SimpleNamespace(prefix="/usr", base_prefix="/usr")
assert in_virtualenv(fake) is False
def test_windows_paths():
"""works the same with Windows paths"""
fake = SimpleNamespace(prefix="C:\\marks\\.venv", base_prefix="C:\\Python314")
assert in_virtualenv(fake) is True
def test_this_python():
"""agrees with the sys module of the Python that runs the tests"""
assert in_virtualenv(sys) == (sys.prefix != sys.base_prefix) A hint
The whole check is one comparison of sys_module.prefix with sys_module.base_prefix. The comparison already gives True or False, so return it directly.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Easy · Python
Read the versions that pip freeze recorded
Write pinned_versions(text). text is what pip freeze printed (the contents of a requirements.txt), and the function returns a dict that maps each package name to its exact version.
- A line such as
marksheet==1.0.0gives{"marksheet": "1.0.0"}. - Blank lines and comments (lines that start with
#) are skipped, and so are spaces around a line. - A package installed straight from a file appears as
marksheet @ file:///…/marksheet-1.0.0-py3-none-any.whl#sha256=…, with no==: skip that line too, because it records a file, not a version. - Empty text gives an empty dict.
Starter code · frozen.py
def pinned_versions(text):
"""Return {name: version} for every name==version line of pip freeze's output."""
versions = {}
return versions The sample tests · test_frozen.py
from frozen import pinned_versions
def test_two_pins():
"""reads every name==version line"""
text = "marksheet==1.0.0\ntablekit==2.3.1\n"
assert pinned_versions(text) == {"marksheet": "1.0.0", "tablekit": "2.3.1"}
def test_blank_lines_and_comments():
"""skips blank lines and comments"""
text = "# written by pip freeze\n\nmarksheet==1.0.0\n"
assert pinned_versions(text) == {"marksheet": "1.0.0"}
def test_spaces_around_a_line():
"""ignores spaces around a line"""
assert pinned_versions(" marksheet==1.0.0 \n") == {"marksheet": "1.0.0"}
def test_a_file_instead_of_a_version():
"""skips a line that records a file instead of a version"""
text = "marksheet @ file:///home/asha/wheels/marksheet-1.0.0-py3-none-any.whl\ntablekit==2.3.1\n"
assert pinned_versions(text) == {"tablekit": "2.3.1"}
def test_empty_text():
"""gives an empty dict for empty text"""
assert pinned_versions("") == {} A hint
Loop over text.splitlines(), strip() each line, and continue when it is empty, starts with # or has no ==. line.split("==", 1) then gives the name and the version.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
When both steps pass, try the questions below. They pick the idea of this lesson you are least sure of, and the feedback on a wrong choice names what it overlooks:
Practice that adapts to you
Practise this lesson
11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): what is a virtual environment, and why use one for each project?
A virtual environment is a folder, usually .venv inside the project, with its own python command and its own
site-packages folder, made by python3 -m venv .venv. Its pyvenv.cfg points to the base Python, whose
interpreter and standard library it uses, so only the packages are its own. One environment per project means each
project gets exactly the versions it needs: one can use version 1 of a library while another uses version 2, an
upgrade in one cannot break the other, and the system’s own Python stays untouched. The environment can be deleted and
rebuilt from requirements.txt at any time, which is why it is never committed: you commit the list of packages, not
the folder.
Two harder questions start from this lesson: a server that refuses pip install, and a scheduled job that cannot find
a package your terminal can. Work out your first step before you open each one:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
On a new Debian 12 server, python3 -m pip install fails with error: externally-managed-environment. A teammate suggests sudo pip install --break-system-packages. What do you do instead, for a web app the server runs and for a command-line tool you use there, and why?
-
Fresher to mid
pip install requests worked in your terminal, and your script runs there. The same script, started by a nightly scheduled job, stops with ModuleNotFoundError: No module named 'requests'. Explain why, and fix it so that it cannot happen again.
Key takeaways
- A virtual environment is a folder (
.venv) with its ownpythonandsite-packages, built on a base Python whose interpreter and standard library it shares; create it withpython3 -m venv .venv, orpy -m venv .venvon Windows. - Activating puts the environment’s
binfolder first onPATH;.venv/bin/pythonruns a program in the environment without activating anything. python -m pipinstalls into the environment of the python that runs it, and only there, which is why the base Python cannot import the package.error: externally-managed-environmentmeans that the Python belongs to the system: use a virtual environment or pipx, neversudo pipor--break-system-packages.- Record exact versions with
pip freeze > requirements.txt, and rebuild an environment from that file instead of moving or copying it, because installed scripts hold its full path. sys.prefix != sys.base_prefixtells a program that it runs inside a virtual environment.
References
- The Python Tutorial: Virtual Environments and Packages (Python Software Foundation)
- venv, creation of virtual environments (Python Software Foundation)
- The initialization of the sys.path module search path (Python Software Foundation)
- site, site-specific configuration hook (Python Software Foundation)
- Install packages in a virtual environment using pip and venv (Python Packaging Authority)
- pip install (pip 26.2.1 documentation) (Python Packaging Authority)
- Externally managed environments (specification) (Python Packaging Authority)
- PEP 668: Marking Python base environments as externally managed (Python Software Foundation)
- pip 26.2.1 source: the externally-managed-environment error (Python Packaging Authority)
- Release notes for Debian 12: Python interpreters marked externally-managed (Debian)
- Ubuntu for developers: set up Python (Canonical)
- Homebrew: language runtimes and packages (Homebrew)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress