Skip to content
aviral gupta

// I5.4 · ~30 min · Intermediate

Virtual environments and pip

By the end you can give each project its own virtual environment, install packages into it and record them so anyone can rebuild it.

Lesson 4 of 5 in I5 Testing and project tooling

You will be able to

  • Create, activate and deactivate a virtual environment, and tell whether Python runs inside one
  • Install and remove packages with python -m pip in the active environment, pinning a version when it matters
  • Record dependencies in requirements.txt, reinstall them with -r, and keep .venv itself out of Git
  1. Warm-up · Activity 1 of 7

    Warm-up from the last lesson: total(numbers) returns the sum of a list. Which input is the edge case most worth its own test?

  2. Predict · Activity 2 of 7

    Predict it before reading on. This creates an environment called demo and asks its interpreter for sys.prefix (Linux or macOS). What does it print?

    import subprocess
    import tempfile
    import venv
    from pathlib import Path
    
    with tempfile.TemporaryDirectory() as tmp:
        env = Path(tmp) / "demo"
        venv.create(env, with_pip=False)
        python = env / "bin" / "python"
        out = subprocess.run([str(python), "-c", "import sys; print(sys.prefix)"], capture_output=True, text=True)
        print(Path(out.stdout.strip()).name)
  3. Practice · Activity 3 of 7

    Fill in the standard-library module that creates a virtual environment in the folder .venv.

    python -m ____ .venv
    python -m .venv
  4. Practice · Activity 4 of 7

    Match each shell command to what it does.

  5. Practice · Activity 5 of 7

    The prompt shows (.venv). You run python -m pip install requests. Where does requests go?

  6. Brain teaser · Activity 6 of 7

    Puzzle. An environment made with with_pip=False tries to import pip (Linux or macOS). What does it print?

    import subprocess
    import tempfile
    import venv
    
    with tempfile.TemporaryDirectory() as tmp:
        venv.create(tmp, with_pip=False)
        result = subprocess.run([tmp + "/bin/python", "-c", "import pip"], capture_output=True)
        print(result.returncode)
  7. Apply · Activity 7 of 7

    Small task. Write setup_env.py for a project folder: it creates .venv with pip using the venv module unless .venv/pyvenv.cfg already exists, then prints the activate command for the platform it runs on. Run it twice on your machine: the second run must not recreate the environment.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

Look inside a new environment

This program asks whether it runs in a virtual environment, then creates one in a temporary folder, which is what python -m venv .venv does, but without pip so it is quick. It lists the keys in pyvenv.cfg and runs the new environment's interpreter to ask the same question. Run it on your machine with the system Python, then again with an activated environment, and compare the first line.

main.py

import subprocess
import sys
import tempfile
import venv
from pathlib import Path


def in_venv() -> bool:
    """True when this interpreter runs from a virtual environment."""
    return sys.prefix != sys.base_prefix


def env_python(env: Path) -> Path:
    """The interpreter inside a virtual environment."""
    if sys.platform == "win32":
        return env / "Scripts" / "python.exe"
    return env / "bin" / "python"


print("running in a venv:", in_venv())

with tempfile.TemporaryDirectory() as tmp:
    env = Path(tmp) / ".venv"
    # python -m venv .venv does the same; with_pip=False skips installing pip.
    venv.create(env, with_pip=False)
    config = (env / "pyvenv.cfg").read_text()
    keys = [line.split("=")[0].strip() for line in config.splitlines() if "=" in line]
    print("pyvenv.cfg keys:", sorted(keys))
    check = subprocess.run(
        [str(env_python(env)), "-c", "import sys; print(sys.prefix != sys.base_prefix)"],
        capture_output=True,
        text=True,
        check=True,
    )
    print("new env's python in a venv:", check.stdout.strip())

Run it with

python main.py

Output

running in a venv: False
pyvenv.cfg keys: ['command', 'executable', 'home', 'include-system-site-packages', 'version']
new env's python in a venv: True
  • The first line is False with the system Python and True when you start the program from an activated environment.
  • home and executable in pyvenv.cfg point back to the Python the environment was built from.
  • include-system-site-packages is false by default, so the environment sees none of the system's packages.
  • The new interpreter reports True without any activation: running it by its full path is enough.

Exercises

Exercise 1 of 2

Where am I running?

Complete two helpers. in_venv() returns True when the running interpreter is inside a virtual environment, using the check the venv docs recommend. env_python(env, platform) returns the path of the interpreter inside the environment folder env: Scripts/python.exe when platform is "win32", otherwise bin/python. Run it on your machine with and without an activated environment and watch the first line change.

This exercise needs Python on your computer (the browser version cannot run it). The files and commands are below.

Hints
  1. Hint 1

    The venv docs: it is sufficient to check sys.prefix != sys.base_prefix.

  2. Hint 2

    Compare the platform argument, not sys.platform, so the tests can ask about Windows on any machine.

  3. Hint 3

    if platform == "win32": return env / "Scripts" / "python.exe"

Show a solution

One way to solve it. Yours can look different and still pass the checks.

import sys
from pathlib import Path


def in_venv() -> bool:
    """True when this interpreter runs from a virtual environment."""
    return sys.prefix != sys.base_prefix


def env_python(env: Path, platform: str = sys.platform) -> Path:
    """The interpreter inside the virtual environment env, for this platform."""
    if platform == "win32":
        return env / "Scripts" / "python.exe"
    return env / "bin" / "python"


if __name__ == "__main__":
    print("in a venv:", in_venv())
    print("interpreter:", env_python(Path(".venv")))
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

import sys
from pathlib import Path


def in_venv() -> bool:
    """True when this interpreter runs from a virtual environment."""
    # Compare the two prefixes the venv docs name.
    return False


def env_python(env: Path, platform: str = sys.platform) -> Path:
    """The interpreter inside the virtual environment env, for this platform."""
    # Windows ("win32") keeps it somewhere else.
    return env / "bin" / "python"


if __name__ == "__main__":
    print("in a venv:", in_venv())
    print("interpreter:", env_python(Path(".venv")))

test_main.py

import sys
from pathlib import Path
from unittest.mock import patch

import main


def test_in_venv_true():
    """in_venv() is True when sys.prefix differs from sys.base_prefix"""
    with patch.object(sys, "prefix", "/project/.venv"), patch.object(sys, "base_prefix", "/usr/local"):
        assert main.in_venv() is True, "the prefixes differ, so this is a virtual environment"


def test_in_venv_false():
    """in_venv() is False when both prefixes are the same"""
    with patch.object(sys, "prefix", "/usr/local"), patch.object(sys, "base_prefix", "/usr/local"):
        assert main.in_venv() is False, "the prefixes are equal, so this is not a virtual environment"


def test_posix_path():
    """On Linux and macOS the interpreter is bin/python"""
    for platform in ("linux", "darwin"):
        got = main.env_python(Path(".venv"), platform)
        assert got == Path(".venv", "bin", "python"), f"{platform}: got {got}"


def test_windows_path():
    """On Windows the interpreter is Scripts/python.exe"""
    got = main.env_python(Path(".venv"), "win32")
    assert got == Path(".venv", "Scripts", "python.exe"), f"win32: got {got}"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 2 of 2

Create an environment from Python

make_env(path) should create a virtual environment at path with the venv module, without pip so it is fast, and return the path of its interpreter. The starter only computes the path. The tests create environments in temporary folders and run the returned interpreter to confirm it reports sys.prefix != sys.base_prefix. Run it locally: it leaves a .venv in the current folder.

This exercise needs Python on your computer (the browser version cannot run it). The files and commands are below.

Hints
  1. Hint 1

    venv.create(path) does what python -m venv path does on the command line.

  2. Hint 2

    Pass with_pip=False: installing pip takes several seconds and the tests do not need it.

  3. Hint 3

    Call venv.create(path, with_pip=False) as the first line of make_env, before the return.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

import sys
import venv
from pathlib import Path


def make_env(path: Path) -> Path:
    """Create a virtual environment at path and return its interpreter."""
    venv.create(path, with_pip=False)
    if sys.platform == "win32":
        return path / "Scripts" / "python.exe"
    return path / "bin" / "python"


if __name__ == "__main__":
    python = make_env(Path(".venv"))
    print("created:", python.exists())
    print("run it with:", python)
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

import sys
import venv
from pathlib import Path


def make_env(path: Path) -> Path:
    """Create a virtual environment at path and return its interpreter."""
    # Create the environment first, without pip, then return its interpreter.
    if sys.platform == "win32":
        return path / "Scripts" / "python.exe"
    return path / "bin" / "python"


if __name__ == "__main__":
    python = make_env(Path(".venv"))
    print("created:", python.exists())
    print("run it with:", python)

test_main.py

import subprocess
import tempfile
from pathlib import Path

import main


def test_creates_environment():
    """make_env writes pyvenv.cfg into the new folder"""
    with tempfile.TemporaryDirectory() as tmp:
        env = Path(tmp) / ".venv"
        main.make_env(env)
        assert (env / "pyvenv.cfg").exists(), "no pyvenv.cfg: call venv.create(path, with_pip=False)"


def test_returns_interpreter():
    """make_env returns the path of the new interpreter, and it exists"""
    with tempfile.TemporaryDirectory() as tmp:
        python = main.make_env(Path(tmp) / ".venv")
        assert python.exists(), f"{python} does not exist"


def test_interpreter_is_in_the_venv():
    """The returned interpreter reports that it runs in a virtual environment"""
    with tempfile.TemporaryDirectory() as tmp:
        python = main.make_env(Path(tmp) / ".venv")
        check = subprocess.run(
            [str(python), "-c", "import sys; print(sys.prefix != sys.base_prefix)"],
            capture_output=True,
            text=True,
        )
        assert check.stdout.strip() == "True", f"{python} printed {check.stdout.strip()!r}{check.stderr}"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Common mistakes

Typing a shell command into Python

# Setting up the project
python -m venv .venv

What Python prints

SyntaxError: invalid syntax

Why, and the fix

python -m venv .venv, source .venv/bin/activate and pip install are commands for the terminal (bash, zsh, PowerShell), not Python code. Typed into a .py file or the >>> prompt they are a syntax error. Run them at the shell prompt; from inside Python, use the venv module or subprocess.

Installed, but into another Python

import requests

print(requests.__version__)

What Python prints

ModuleNotFoundError: No module named 'requests'

Why, and the fix

You installed requests, but into a different environment than the python running this file: perhaps pip belonged to another Python, or the environment was not activated in this terminal. Activate .venv, check with python -c "import sys; print(sys.prefix)", and install with python -m pip install requests so pip and python are the same interpreter.

A misspelled requirements file

import subprocess
import sys

subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirments.txt"], check=True)

What Python prints

ERROR: Could not open requirements file: [Errno 2] No such file or directory: 'requirments.txt'

Why, and the fix

pip install -r reads the file you name, relative to the current folder, and stops if it is not there; here requirments.txt is missing an e. Check the name and run the command from the project folder. check=True then turns pip's non-zero exit status into CalledProcessError, so a script cannot carry on as if the install worked.

Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

One environment per project

A virtual environment is a folder with its own Python interpreter and its own site-packages, so two projects can need different versions of a package without clashing. Create one in the project folder with python -m venv .venv; .venv is the usual name. Inside it, bin (Scripts on Windows) holds the interpreter and the activate scripts, and pyvenv.cfg records which Python it was built from. The folder is disposable: delete it and create it again whenever it breaks.

Activating is a shortcut

source .venv/bin/activate (Linux, macOS) or .venv\Scripts\activate (Windows) puts the environment first on PATH, so python and pip mean its copies; the prompt shows (.venv), and deactivate undoes it. Activation is optional: .venv/bin/python app.py uses the environment directly. Inside a program, sys.prefix != sys.base_prefix is True exactly when Python runs from a virtual environment.

pip and requirements.txt

python -m pip install requests installs a package from the Python Package Index into the environment of the python you ran; requests==2.32.3 pins one version. Prefer python -m pip to a bare pip: it cannot pick another Python by accident. python -m pip freeze > requirements.txt records what is installed, and python -m pip install -r requirements.txt installs it all again. Commit requirements.txt, never .venv.

Sources

Last reviewed September 29, 2026