Skip to main content

Python venv and virtualenv Practice Problems & Exercises

Practice: venv and virtualenv

11 problems4 Easy4 Medium3 Hard40-55 min
← Back to lesson

Easy

#1Detect Active Virtual EnvironmentEasy
sys.prefixos.environvenv-detection

Write a function that detects whether the current Python process is running inside a virtual environment. Use two independent checks: comparing sys.prefix to sys.base_prefix, and inspecting the VIRTUAL_ENV environment variable.

Python
import sys
import os

def is_venv_active():
    prefix_check = sys.prefix != sys.base_prefix
    env_check = os.environ.get("VIRTUAL_ENV") is not None
    return prefix_check or env_check

print(is_venv_active())
Solution
import sys
import os

def is_venv_active():
prefix_check = sys.prefix != sys.base_prefix
env_check = os.environ.get("VIRTUAL_ENV") is not None
return prefix_check or env_check

sys.base_prefix is the canonical check. It was added in Python 3.3 specifically for this purpose and always points to the system Python installation, even when inside a venv. sys.prefix is redirected to the venv directory when a venv is active. The VIRTUAL_ENV variable is set by the activate shell script — it is not present when you use python -m venv and run the interpreter directly without activating.

import sys
import os

def is_venv_active():
    """Return True if the current Python process is running
    inside a virtual environment, False otherwise.
    Check both sys.prefix != sys.base_prefix and the
    VIRTUAL_ENV environment variable.
    """
    # TODO: implement
    pass
Expected Output
True
Hints

Hint 1: In a venv, sys.prefix points to the venv directory while sys.base_prefix points to the system Python.

Hint 2: Also check os.environ.get("VIRTUAL_ENV") — this is set by the activate script.

#2Parse pyvenv.cfgEasy
pyvenv.cfgconfig-parsingvenv-internals

Parse a pyvenv.cfg file from its string content. This file lives at the root of every venv and records where the base Python lives, whether system site-packages are included, and which Python version was used.

Python
def parse_pyvenv_cfg(content):
    result = {}
    for line in content.splitlines():
        line = line.strip()
        if not line or line.startswith('#'):
            continue
        if '=' in line:
            key, _, value = line.partition('=')
            result[key.strip()] = value.strip()
    return result

sample = """home = /usr/bin
include-system-site-packages = false
version = 3.11.5"""

print(parse_pyvenv_cfg(sample))
Solution
def parse_pyvenv_cfg(content):
result = {}
for line in content.splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, _, value = line.partition('=')
result[key.strip()] = value.strip()
return result

str.partition('=') splits on the first = only, which is correct because values could theoretically contain = signs. The pyvenv.cfg file is CPython's record of the venv's parentage — the home field is what Python uses to compute sys.base_prefix at startup. When you run python inside a venv, the interpreter walks up from the executable location to find this file and configure itself.

def parse_pyvenv_cfg(content):
    """Parse the contents of a pyvenv.cfg file.
    
    Each non-blank, non-comment line has the form:
        key = value
    Return a dict of {key: value} with leading/trailing
    whitespace stripped from both key and value.
    
    Example input:
        home = /usr/bin
        include-system-site-packages = false
        version = 3.11.5
    """
    # TODO: implement
    pass
Expected Output
{'home': '/usr/bin', 'include-system-site-packages': 'false', 'version': '3.11.5'}
Hints

Hint 1: Split each line on "=" but only on the first "=" to handle values that may contain "=".

Hint 2: Skip blank lines and lines starting with "#".

#3Classify Python Executable PathEasy
sys.executablePATHvenv-layout

Write a classifier that inspects a Python executable path and categorizes it as coming from a plain venv, pyenv, conda, or the system. Use string matching only — no file I/O.

Python
import os

def classify_python_path(executable_path):
    p = executable_path
    if '.pyenv' in p:
        return 'pyenv'
    if 'conda' in p or '/envs/' in p:
        return 'conda'
    if ('venv' in p or '.venv' in p or '/bin/python' in p) and 'base_prefix' not in p:
        if '/usr/bin' not in p and '/usr/local/bin' not in p:
            return 'venv'
    return 'system'

paths = [
    '/home/user/projects/myapp/.venv/bin/python3',
    '/home/user/.pyenv/versions/3.11.5/bin/python',
    '/home/user/miniconda3/envs/ml/bin/python',
    '/usr/bin/python3',
]
for p in paths:
    print(classify_python_path(p))
Solution
def classify_python_path(executable_path):
p = executable_path
if '.pyenv' in p:
return 'pyenv'
if 'conda' in p or '/envs/' in p:
return 'conda'
if 'venv' in p or '.venv' in p:
return 'venv'
return 'system'

Path classification is a common sysadmin and tooling task. Real tools like pip and IDEs use this kind of heuristic to display the active environment. The order of checks matters: check for pyenv first because pyenv paths never contain "venv" or "conda", then conda (it uses /envs/), then generic venv patterns. sys.executable in a real program gives the full path to the running Python binary — this is more reliable than which python because it reflects the actual interpreter regardless of PATH manipulation.

import os

def classify_python_path(executable_path):
    """Given a Python executable path string, classify it.
    Return one of:
    - 'venv'    if path contains '/bin/python' inside a venv-like dir
    - 'pyenv'   if path contains '.pyenv'
    - 'conda'   if path contains 'conda' or 'envs'
    - 'system'  otherwise
    
    Use simple string inspection — no filesystem access.
    """
    # TODO: implement
    pass
Expected Output
venv
pyenv
conda
system
Hints

Hint 1: Check for substrings: ".pyenv" for pyenv, "conda" or "/envs/" for conda, "/lib/python" combined with "venv" for venv.

Hint 2: Order your checks carefully — a conda env path might also contain "lib/python".

#4Generate activate Script FragmentsEasy
activate-scriptPATHVIRTUAL_ENV

Model the key variable assignments that a venv activate script performs. This helps understand why activating a venv changes your shell environment — it mutates PATH and VIRTUAL_ENV, and modifies PS1 to show the env name in the prompt.

Python
import os

def generate_activate_fragment(venv_path):
    venv_name = os.path.basename(venv_path)
    return {
        'VIRTUAL_ENV': venv_path,
        'PATH': venv_path + '/bin:$PATH',
        'PS1': '(' + venv_name + ') $PS1',
    }

print(generate_activate_fragment('/home/user/projects/.venv'))
Solution
import os

def generate_activate_fragment(venv_path):
venv_name = os.path.basename(venv_path)
return {
'VIRTUAL_ENV': venv_path,
'PATH': venv_path + '/bin:$PATH',
'PS1': '(' + venv_name + ') $PS1',
}

Activation is purely a PATH manipulation. The activate script prepends venv/bin to PATH so that python and pip resolve to the venv executables instead of the system ones. It also saves and later restores the old PATH and PS1 in _OLD_VIRTUAL_PATH and _OLD_VIRTUAL_PS1 so deactivate can undo the changes. This means activation is entirely optional — you can always invoke the venv's Python directly via ./venv/bin/python without ever sourcing the activate script.

def generate_activate_fragment(venv_path):
    """Return the key shell variable assignments from
    a venv activate script for a given venv_path.
    
    Return a dict with keys:
    - 'VIRTUAL_ENV': the venv_path itself
    - 'PATH': venv_path + '/bin' prepended to a placeholder '$PATH'
    - 'PS1': '(venv_name) ' prepended to '$PS1'
    
    venv_name is the last component of venv_path.
    """
    # TODO: implement
    pass
Expected Output
{'VIRTUAL_ENV': '/home/user/projects/.venv', 'PATH': '/home/user/projects/.venv/bin:$PATH', 'PS1': '(.venv) $PS1'}
Hints

Hint 1: Use os.path.basename(venv_path) to extract the venv name.

Hint 2: PATH is venv_path + "/bin" + ":" + "$PATH".


Medium

#5Venv Site-Packages InspectorMedium
site-packagessys.pathisolation

Write two utility functions: one that extracts all site-packages directories from sys.path, and one that counts installed packages in a given site-packages directory by counting .dist-info entries.

Python
import sys
import os

def get_site_packages_dirs():
    return sorted(p for p in sys.path if 'site-packages' in p)

def count_installed_packages(site_packages_dir):
    if not os.path.isdir(site_packages_dir):
        return 0
    return sum(
        1 for entry in os.listdir(site_packages_dir)
        if entry.endswith('.dist-info')
    )

dirs = get_site_packages_dirs()
print("site_packages found:", len(dirs) >= 0)
if dirs:
    count = count_installed_packages(dirs[0])
    print("package count >= 0:", count >= 0)
else:
    print("package count >= 0: True")
Solution
import sys
import os

def get_site_packages_dirs():
return sorted(p for p in sys.path if 'site-packages' in p)

def count_installed_packages(site_packages_dir):
if not os.path.isdir(site_packages_dir):
return 0
return sum(
1 for entry in os.listdir(site_packages_dir)
if entry.endswith('.dist-info')
)

Each installed package leaves a .dist-info directory containing metadata like METADATA, WHEEL, and RECORD. This is the canonical way pip tracks what is installed — pip list reads these directories rather than scanning Python files. A fresh venv typically has just pip and setuptools (2-3 .dist-info dirs). This approach is the basis of tools like pip list --format=json and importlib.metadata.

import sys

def get_site_packages_dirs():
    """Return a list of all site-packages directories
    currently on sys.path.
    Filter for paths that contain 'site-packages'.
    Sort the result alphabetically.
    """
    # TODO: implement
    pass

def count_installed_packages(site_packages_dir):
    """Count the number of installed packages in a
    site-packages directory by counting .dist-info dirs.
    Return 0 if the directory does not exist.
    """
    import os
    # TODO: implement
    pass
Expected Output
site_packages found: True
package count >= 0: True
Hints

Hint 1: Filter sys.path for entries containing the string "site-packages".

Hint 2: Count directories ending in ".dist-info" — each represents one installed package.

#6Cross-Platform Python Binary PathMedium
cross-platformos.namevenv-layout

Virtual environments have different internal layouts on Windows vs Unix. Write a function that returns the correct Python executable path for a given venv directory, supporting both platforms.

This matters when writing tooling that manipulates venvs programmatically (CI scripts, IDE plugins, build tools).

Python
import os

def get_venv_python(venv_dir, platform=None):
    if platform is None:
        platform = 'win32' if os.name == 'nt' else 'unix'
    if platform == 'win32':
        return os.path.join(venv_dir, 'Scripts', 'python.exe')
    return os.path.join(venv_dir, 'bin', 'python3')

print(get_venv_python('/home/user/.venv', platform='unix'))
print(get_venv_python('C:/projects/.venv', platform='win32'))
Solution
import os

def get_venv_python(venv_dir, platform=None):
if platform is None:
platform = 'win32' if os.name == 'nt' else 'unix'
if platform == 'win32':
return os.path.join(venv_dir, 'Scripts', 'python.exe')
return os.path.join(venv_dir, 'bin', 'python3')

The Windows venv layout uses Scripts\ instead of bin\ — a consequence of Windows not having a POSIX /usr/bin convention. This difference breaks many shell scripts that hardcode venv/bin/python. Robust tooling always uses os.path.join with platform detection or uses sysconfig.get_path('scripts') to get the correct scripts directory for the current platform. The virtualenv package adds a compatibility layer that creates both layouts on Windows.

import os

def get_venv_python(venv_dir, platform=None):
    """Return the path to the Python executable inside
    a venv directory for the given platform.
    
    platform: 'win32', 'linux', or 'darwin'
    If platform is None, use os.name to detect.
    
    Windows layout:  venv_dir/Scripts/python.exe
    Unix layout:     venv_dir/bin/python3
    
    """
    # TODO: implement
    pass
Expected Output
/home/user/.venv/bin/python3
C:/projects/.venv/Scripts/python.exe
Hints

Hint 1: On Windows (os.name == "nt"), the layout is Scripts/ and the binary is python.exe.

Hint 2: On Unix systems, the layout is bin/ and the binary is python3.

#7Dependency Isolation ValidatorMedium
importlibisolationsys.path

Write a function that checks whether a package is available in the current environment and whether its location is inside a venv. This is useful for pre-flight checks in scripts that depend on optional packages.

Python
import sys
import importlib.util

def check_package_available(package_name):
    spec = importlib.util.find_spec(package_name)
    if spec is None:
        return {'available': False, 'location': None, 'in_venv': False}
    location = spec.origin or ''
    in_venv = (
        'site-packages' in location and
        sys.prefix != sys.base_prefix
    )
    return {
        'available': True,
        'location': location,
        'in_venv': in_venv,
    }

result = check_package_available('os')
print(result['available'], result['location'] is not None)
Solution
import sys
import importlib.util

def check_package_available(package_name):
spec = importlib.util.find_spec(package_name)
if spec is None:
return {'available': False, 'location': None, 'in_venv': False}
location = spec.origin or ''
in_venv = (
'site-packages' in location and
sys.prefix != sys.base_prefix
)
return {
'available': True,
'location': location,
'in_venv': in_venv,
}

importlib.util.find_spec is the correct way to check package availability without actually importing the package (which can have side effects). spec.origin gives the path to the package's __init__.py or the .py file for single-module packages. Standard library modules will have a location inside the Python installation, not site-packages. This pattern is used by tools like pytest to detect optional plugins at startup.

import sys
import importlib.util

def check_package_available(package_name):
    """Check whether a package is importable in the
    current Python environment.
    Return a dict with:
    - 'available': bool
    - 'location': str path to the package or None
    - 'in_venv': bool (True if location is inside a venv)
    """
    # TODO: implement
    pass
Expected Output
{'available': True, 'location': '...', 'in_venv': True or False}
Hints

Hint 1: Use importlib.util.find_spec(package_name) — it returns None if the package is not importable.

Hint 2: Check if the spec.origin path contains "site-packages" to determine if it is installed (vs stdlib).

#8Reconstruct sys.path for a VenvMedium
sys.pathvenv-layoutsite-packages

Model how Python constructs sys.path inside a venv. When include-system-site-packages is false (the default), only the venv's own site-packages is on the path. When true, system paths are also included — useful for environments that need system-level packages like apt-installed libraries.

Python
import os

def build_venv_sys_path(venv_dir, python_version, include_system=False):
    paths = [
        os.path.join(venv_dir, 'lib', 'python' + python_version, 'site-packages')
    ]
    if include_system:
        paths.extend([
            '/usr/lib/python' + python_version,
            '/usr/lib/python' + python_version + '/lib-dynload',
        ])
    return paths

print(build_venv_sys_path('/home/user/.venv', '3.11'))
print(build_venv_sys_path('/home/user/.venv', '3.11', include_system=True))
Solution
import os

def build_venv_sys_path(venv_dir, python_version, include_system=False):
paths = [
os.path.join(venv_dir, 'lib', 'python' + python_version, 'site-packages')
]
if include_system:
paths.extend([
'/usr/lib/python' + python_version,
'/usr/lib/python' + python_version + '/lib-dynload',
])
return paths

The include-system-site-packages flag in pyvenv.cfg controls whether system paths are appended. The default is false, which gives full isolation — nothing from the system Python leaks in. Setting it to true is useful in Docker images where some heavy packages (numpy, scipy, GDAL) are installed via the OS package manager and you do not want to reinstall them in every venv. You can set this at creation time with python -m venv --system-site-packages myenv.

import os

def build_venv_sys_path(venv_dir, python_version, include_system=False):
    """Build the expected sys.path entries for a venv.
    
    Always include:
    - venv_dir/lib/pythonX.Y/site-packages
    
    If include_system is True, also include:
    - /usr/lib/pythonX.Y
    - /usr/lib/pythonX.Y/lib-dynload
    
    python_version: string like '3.11'
    Return a list of path strings.
    """
    # TODO: implement
    pass
Expected Output
['/home/user/.venv/lib/python3.11/site-packages']
Hints

Hint 1: Format the lib directory as: venv_dir + "/lib/python" + python_version + "/site-packages"

Hint 2: The include_system flag corresponds to include-system-site-packages = true in pyvenv.cfg.


Hard

#9Venv Staleness DetectorHard
pyvenv.cfgversion-parsingvenv-maintenance

Write a staleness checker for virtual environments. A venv is considered stale when the Python version it was created with differs in major or minor version from the currently active Python. Patch version differences are acceptable.

This is the kind of check that IDE integrations and CI systems perform before using a cached venv.

Python
import os

def check_venv_staleness(venv_dir, current_python_version):
    cfg_path = os.path.join(venv_dir, 'pyvenv.cfg')

    if not os.path.exists(cfg_path):
        return {
            'stale': True,
            'venv_version': None,
            'current_version': current_python_version,
            'reason': 'pyvenv.cfg not found',
        }

    venv_version = None
    try:
        with open(cfg_path) as f:
            for line in f:
                line = line.strip()
                if line.startswith('version'):
                    _, _, val = line.partition('=')
                    venv_version = val.strip()
                    break
    except OSError:
        return {
            'stale': True,
            'venv_version': None,
            'current_version': current_python_version,
            'reason': 'could not read pyvenv.cfg',
        }

    if venv_version is None:
        return {
            'stale': True,
            'venv_version': None,
            'current_version': current_python_version,
            'reason': 'version field missing from pyvenv.cfg',
        }

    def major_minor(v):
        parts = v.split('.')
        return (parts[0], parts[1]) if len(parts) >= 2 else (v, '')

    if major_minor(venv_version) != major_minor(current_python_version):
        return {
            'stale': True,
            'venv_version': venv_version,
            'current_version': current_python_version,
            'reason': 'version mismatch: venv=' + venv_version + ', current=' + current_python_version,
        }

    return {
        'stale': False,
        'venv_version': venv_version,
        'current_version': current_python_version,
        'reason': 'versions compatible',
    }

# Simulate a venv with wrong version
import tempfile, os
with tempfile.TemporaryDirectory() as tmpdir:
    cfg = os.path.join(tmpdir, 'pyvenv.cfg')
    with open(cfg, 'w') as f:
        f.write('home = /usr/bin\nversion = 3.10.4\n')
    result = check_venv_staleness(tmpdir, '3.11.5')
    print(result)
Solution
import os

def check_venv_staleness(venv_dir, current_python_version):
cfg_path = os.path.join(venv_dir, 'pyvenv.cfg')
if not os.path.exists(cfg_path):
return {'stale': True, 'venv_version': None,
'current_version': current_python_version,
'reason': 'pyvenv.cfg not found'}
venv_version = None
with open(cfg_path) as f:
for line in f:
if line.strip().startswith('version'):
_, _, val = line.partition('=')
venv_version = val.strip()
break
if venv_version is None:
return {'stale': True, 'venv_version': None,
'current_version': current_python_version,
'reason': 'version field missing from pyvenv.cfg'}
def mm(v):
p = v.split('.')
return (p[0], p[1]) if len(p) >= 2 else (v, '')
if mm(venv_version) != mm(current_python_version):
return {'stale': True, 'venv_version': venv_version,
'current_version': current_python_version,
'reason': 'version mismatch: venv=' + venv_version + ', current=' + current_python_version}
return {'stale': False, 'venv_version': venv_version,
'current_version': current_python_version,
'reason': 'versions compatible'}

Stale venvs are a silent CI failure mode. When a Python minor version upgrade happens (3.10 to 3.11), the ABI changes — C extension modules compiled for 3.10 will crash when loaded by 3.11. Patch versions (3.11.4 to 3.11.5) are ABI-compatible. A robust CI pipeline checks pyvenv.cfg before using a cached venv and recreates it on major.minor mismatch. Tools like tox and nox do this automatically.

import os

def check_venv_staleness(venv_dir, current_python_version):
    """Check if a venv is stale (created with a different
    Python version than is currently active).
    
    Read venv_dir/pyvenv.cfg, parse the 'version' field.
    Compare to current_python_version.
    
    Return a dict:
    - 'stale': bool
    - 'venv_version': str (from cfg) or None
    - 'current_version': str (passed in)
    - 'reason': descriptive string
    
    If pyvenv.cfg does not exist, return stale=True with
    reason='pyvenv.cfg not found'.
    """
    # TODO: implement
    pass
Expected Output
{'stale': True, 'venv_version': '3.10.4', 'current_version': '3.11.5', 'reason': 'version mismatch: venv=3.10.4, current=3.11.5'}
Hints

Hint 1: Parse pyvenv.cfg line by line, splitting on "=" to get key-value pairs.

Hint 2: Compare the major.minor components — patch version mismatch is acceptable, major.minor mismatch is stale.

#10Virtual Environment InventoryHard
filesystemvenv-discoverytooling

Write a venv discovery tool that walks a directory tree and finds all virtual environments. This is the kind of utility that IDE workspace scanners, direnv plugins, and project management tools use to auto-detect environments.

Critically, do not recurse into a detected venv — venvs contain their own lib/python3.x/ directories which would produce false positives.

Python
import os

def find_venvs(root_dir, max_depth=3):
    venvs = []
    root_dir = os.path.abspath(root_dir)

    def is_venv(path):
        if os.path.exists(os.path.join(path, 'pyvenv.cfg')):
            return True
        if os.path.exists(os.path.join(path, 'bin', 'python3')):
            return True
        if os.path.exists(os.path.join(path, 'Scripts', 'python.exe')):
            return True
        return False

    for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
        rel = os.path.relpath(dirpath, root_dir)
        depth = 0 if rel == '.' else rel.count(os.sep) + 1
        if depth >= max_depth:
            dirnames.clear()
            continue
        to_remove = []
        for d in dirnames:
            full = os.path.join(dirpath, d)
            if is_venv(full):
                venvs.append(full)
                to_remove.append(d)
        for d in to_remove:
            dirnames.remove(d)

    return sorted(venvs)

import tempfile
with tempfile.TemporaryDirectory() as tmp:
    os.makedirs(os.path.join(tmp, 'project1', '.venv', 'bin'), exist_ok=True)
    open(os.path.join(tmp, 'project1', '.venv', 'pyvenv.cfg'), 'w').close()
    os.makedirs(os.path.join(tmp, 'project2', 'env'), exist_ok=True)
    open(os.path.join(tmp, 'project2', 'env', 'pyvenv.cfg'), 'w').close()
    found = find_venvs(tmp)
    print("Found", len(found), "venvs")
Solution
import os

def find_venvs(root_dir, max_depth=3):
venvs = []
root_dir = os.path.abspath(root_dir)

def is_venv(path):
return (
os.path.exists(os.path.join(path, 'pyvenv.cfg')) or
os.path.exists(os.path.join(path, 'bin', 'python3')) or
os.path.exists(os.path.join(path, 'Scripts', 'python.exe'))
)

for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
rel = os.path.relpath(dirpath, root_dir)
depth = 0 if rel == '.' else rel.count(os.sep) + 1
if depth >= max_depth:
dirnames.clear()
continue
to_remove = []
for d in list(dirnames):
full = os.path.join(dirpath, d)
if is_venv(full):
venvs.append(full)
to_remove.append(d)
for d in to_remove:
dirnames.remove(d)

return sorted(venvs)

The key trick is mutating dirnames in-place during os.walk with topdown=True. When you remove an entry from dirnames, os.walk skips descending into it. This prevents the scanner from walking into venv/lib/python3.11/site-packages/numpy/... — which contains thousands of files and would make the scan extremely slow. This pattern (walk + prune) is used by tools like fd, ripgrep, and VS Code's file watcher.

import os

def find_venvs(root_dir, max_depth=3):
    """Walk the directory tree under root_dir (up to max_depth)
    and find all virtual environments.
    
    A directory is a venv if it contains:
    - pyvenv.cfg at its root, OR
    - bin/python3 (Unix) or Scripts/python.exe (Windows)
    
    Return a list of absolute paths to venv roots.
    Do NOT recurse into venv directories themselves.
    """
    # TODO: implement
    pass
Expected Output
['/projects/api/.venv', '/projects/web/.venv']
Hints

Hint 1: Use os.walk with topdown=True. When you find a venv dir, add it to results and remove it from dirs to prevent recursion into it.

Hint 2: Track current depth by counting os.sep in the relative path from root_dir.

#11Environment Reproducibility ReportHard
reproducibilitysys.pathtoolingmetadata

Generate a complete environment reproducibility snapshot. This is the kind of report that data science notebooks and ML experiment trackers attach to run logs so that results can be reproduced later — knowing exact package versions is critical when a model trained on numpy 1.24 behaves differently on numpy 2.0.

Python
import sys
import importlib.metadata

def generate_env_report():
    vi = sys.version_info
    python_version = str(vi.major) + '.' + str(vi.minor) + '.' + str(vi.micro)

    packages = []
    for dist in importlib.metadata.distributions():
        name = dist.metadata.get('Name', '')
        version = dist.metadata.get('Version', '')
        if name:
            packages.append(name.lower() + '==' + version)
    packages = sorted(set(packages))

    return {
        'python_version': python_version,
        'executable': sys.executable,
        'is_venv': sys.prefix != sys.base_prefix,
        'platform': sys.platform,
        'installed_packages': packages,
        'package_count': len(packages),
    }

report = generate_env_report()
print("python_version set:", bool(report['python_version']))
print("executable set:", bool(report['executable']))
print("is_venv:", report['is_venv'])
print("package_count:", report['package_count'])
Solution
import sys
import importlib.metadata

def generate_env_report():
vi = sys.version_info
python_version = str(vi.major) + '.' + str(vi.minor) + '.' + str(vi.micro)
packages = []
for dist in importlib.metadata.distributions():
name = dist.metadata.get('Name', '')
version = dist.metadata.get('Version', '')
if name:
packages.append(name.lower() + '==' + version)
packages = sorted(set(packages))
return {
'python_version': python_version,
'executable': sys.executable,
'is_venv': sys.prefix != sys.base_prefix,
'platform': sys.platform,
'installed_packages': packages,
'package_count': len(packages),
}

importlib.metadata (stdlib since 3.8) is the proper API for reading installed package metadata — more reliable than parsing pip list output or scanning directories manually. importlib.metadata.distributions() yields every installed distribution found on sys.path. The set() call deduplicates because the same distribution can appear multiple times if it is installed in both a venv and system site-packages (when include-system-site-packages = true). This report format is essentially a machine-readable pip freeze.

import sys
import importlib.metadata

def generate_env_report():
    """Generate a reproducibility report for the current
    Python environment. Return a dict with:
    
    - 'python_version': sys.version_info as 'X.Y.Z'
    - 'executable': sys.executable
    - 'is_venv': bool
    - 'platform': sys.platform
    - 'installed_packages': list of 'name==version' strings,
        sorted alphabetically, from importlib.metadata
    - 'package_count': int
    """
    # TODO: implement
    pass
Expected Output
python_version set, executable set, is_venv bool, packages list
Hints

Hint 1: Use importlib.metadata.packages_distributions() or importlib.metadata.distributions() to iterate installed packages.

Hint 2: Each distribution has .metadata["Name"] and .version attributes.

© 2026 EngineersOfAI. All rights reserved.