Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Analysis with Python (pandas, polars, DuckDB)  Module 1 – The data-analysis workflow and your toolkit

Set up your analysis environment and lock file

Give each analysis its own environment: a uv project, exact versions in a lock file, a check before every run, and the choice between scripts and notebooks.

  • Beginner
  • 20 minutes
  • Examples run with Python 3.14.8, pandas 3.0.6, numpy 2.5.3, polars 1.44.2, duckdb 1.5.6, pyarrow 25.0.1 and Pyodide 314.0.7
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Create a project for an analysis with uv, with exact versions in pyproject.toml and uv.lock
  • Check before every run that the installed packages match the lock file
  • Explain how an upgrade of Python or of a library can change the numbers an analysis prints
  • Choose between a script, a Jupyter notebook and a marimo notebook for a task

Before you start

What you’ll learn · Before you start

What you will learn

  • Create a project for an analysis with uv, with exact versions in pyproject.toml and uv.lock
  • Check before every run that the installed packages match the lock file
  • Explain how an upgrade of Python or of a library can change the numbers an analysis prints
  • Choose between a script, a Jupyter notebook and a marimo notebook for a task

Before you start

On this page

An analysis environment is the Python interpreter plus the exact version of every library your code imports. It decides your numbers as much as your code does: the same file can print a different total under another version of Python or pandas, and nothing in the file shows it. A result someone else can repeat therefore comes with its environment written down.

This lesson sets that up the way this track does it. Each analysis gets its own project folder, created with uv, with the exact versions recorded in a lock file. A short check runs before the analysis and refuses to go on in an environment that does not match. Last, it compares the three common ways to run analysis code: a script, a Jupyter notebook and a marimo notebook.

The same code, different numbers

Here are ten payments of 0.10 each, added up three ways:

Ten payments of 0.10, added three ways Python · sums/payments_sum.py
# Ten payments of 0.10 each, added up three ways.
import math

payments = [0.1] * 10

total = 0.0
for amount in payments:
    total += amount  # one addition at a time, rounded after each step

print("loop, one + at a time:", total)
print("sum():                ", sum(payments))
print("math.fsum():          ", math.fsum(payments))
print("loop total == 1.0:    ", total == 1.0)
print("sum() == 1.0:         ", sum(payments) == 1.0)

Output

loop, one + at a time: 0.9999999999999999
sum():                 1.0
math.fsum():           1.0
loop total == 1.0:     False
sum() == 1.0:          True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 payments_sum.py

The loop adds one payment at a time, and every addition rounds its result to the nearest value a float can hold, so the small errors pile up to 0.9999999999999999. sum() prints 1.0 because Python 3.12 switched it to a more accurate algorithm for floats (sum(), What’s New In Python 3.12). Up to Python 3.11, sum() added from left to right exactly like the loop and printed 0.9999999999999999 as well. A report that compared a sum() with an expected total using == gave a different answer after that upgrade, without a line of its code changing. math.fsum() gives the correctly rounded total in every version. For money, whole paise or cents stored as integers, or decimal.Decimal, avoid the question altogether.

Libraries change more often than Python does. pandas 3.0 stores text columns with a dedicated str type where pandas 2 used NumPy’s general object type, and a str column refuses a value that is not text, so series[0] = 5 raises TypeError where pandas 2 accepted it (pandas 3.0 release notes, string migration guide). Changes like these are announced in each library’s release notes. With the versions written down, you take them when you decide to, not on the day a colleague’s computer happens to install a newer release.

One project per analysis

uv is a free, open-source tool from the company Astral that creates projects and virtual environments, installs packages and records their exact versions; install it once, the way its documentation describes for your operating system. Three uv commands set up an analysis project with the versions this track uses and build it again elsewhere:

Creating the project and building it again from its lock (recorded; paths shortened) Text · uv_session.txt
$ uv init --no-package --python 3.14.8 sales-analysis
Initialized project `sales-analysis` at `~/projects/sales-analysis`

$ cd sales-analysis
$ uv add pandas==3.0.6 polars==1.44.2 duckdb==1.5.6 numpy==2.5.3 pyarrow==25.0.1
Using CPython 3.14.8 interpreter at: …/bin/python3
Creating virtual environment at: .venv
Resolved 10 packages in 444ms
Downloading duckdb (14.8MiB)
Downloading pyarrow (34.2MiB)
Downloading pandas (9.7MiB)
Downloading polars-runtime-32 (41.3MiB)
Downloading numpy (5.2MiB)
 Downloaded numpy
 Downloaded pandas
 Downloaded duckdb
 Downloaded pyarrow
 Downloaded polars-runtime-32
Prepared 8 packages in 5.41s
Installed 8 packages in 120ms
 + duckdb==1.5.6
 + numpy==2.5.3
 + pandas==3.0.6
 + polars==1.44.2
 + polars-runtime-32==1.44.2
 + pyarrow==25.0.1
 + python-dateutil==2.9.0.post0
 + six==1.17.0

$ ls -A
.git
.gitignore
.python-version
.venv
main.py
pyproject.toml
README.md
uv.lock

$ mkdir ../second && cp pyproject.toml uv.lock .python-version ../second/
$ cd ../second
$ uv sync --locked
Using CPython 3.14.8 interpreter at: …/bin/python3
Creating virtual environment at: .venv
Resolved 10 packages in 11ms
Installed 8 packages in 120ms
 + duckdb==1.5.6
 + numpy==2.5.3
 + pandas==3.0.6
 + polars==1.44.2
 + polars-runtime-32==1.44.2
 + pyarrow==25.0.1
 + python-dateutil==2.9.0.post0
 + six==1.17.0

What each step did:

  • uv init --no-package --python 3.14.8 sales-analysis created the folder with a pyproject.toml that describes the project, a .python-version file that pins the interpreter, a starter main.py, a README.md, a .gitignore and a Git repository. --no-package says this is an analysis, not a library that needs to be installable.
  • uv add with exact versions wrote those requirements into pyproject.toml, created the virtual environment .venv with CPython 3.14.8, installed the packages into it and wrote uv.lock. It resolved 10 packages: the five named, polars-runtime-32 (the compiled engine that polars installs), python-dateutil and six (which pandas needs), tzdata (which pandas needs only on Windows and in browsers) and the project itself. On this Mac it installed 8 of them.
  • uv sync --locked built the environment again in a fresh copy of the folder from three files alone, with the same 8 packages at the same versions. That is what a teammate’s computer, or yours next year, does. If pyproject.toml has changed since the lock was written, --locked stops with an error instead of quietly writing a new lock.

After that, uv run python analysis.py runs any file in the project’s environment; uv first checks that .venv still matches the lock and brings it up to date, so you never activate anything by hand.

Two of the files matter most, and both appear in the tabs of the check in the next section. pyproject.toml says what the project wants; here every requirement is an exact == version, which suits an analysis whose numbers must not move. uv.lock records the exact version of every package, the dependencies of your dependencies included, for every platform at once: uv’s documentation calls it a universal lock file. The copy shown in the next section leaves out the lists of download addresses and file hashes that the real file holds for each package.

Commit pyproject.toml, uv.lock and .python-version with your code. Leave .venv out (the .gitignore that uv wrote already does): for this project it takes 387 MB on disk, and uv sync rebuilds it in seconds.

Without uv, Python’s own venv and pip do the same job in more steps: python3 -m venv .venv, then .venv/bin/python -m pip install pandas==3.0.6 and the rest, then .venv/bin/python -m pip freeze > requirements.txt to record the versions, and pip install -r requirements.txt on the next computer. A requirements.txt made this way records what was installed on one computer, not a resolution for every platform.

Check the environment before every run

A lock file only helps if the analysis actually runs in the environment it describes. This program compares the versions in uv.lock with what the running Python has installed, using only the standard library (tomllib reads the lock and importlib.metadata reads the installed versions), lists every difference it finds and then stops with an error. Put it before the analysis: uv run python check_env.py && uv run python analysis.py, or paste it into the first cell of a notebook.

Check the installed versions against uv.lock

env/check_env.py

# Stop an analysis before it runs in the wrong environment: compare what this Python has installed with the
# versions the project's uv.lock records. Run it first, or paste it into the first cell of a notebook.
import importlib.metadata
import platform
import sys
import tomllib

with open("uv.lock", "rb") as f:
    lock = tomllib.load(f)

locked = {p["name"]: p["version"] for p in lock["package"]}
project = next(p for p in lock["package"] if p["source"].get("virtual") == ".")
needed = sorted(dep["name"] for dep in project.get("dependencies", []))

python = platform.python_version()
minimum = lock["requires-python"].removeprefix(">=")
too_old = tuple(map(int, python.split("."))) < tuple(map(int, minimum.split(".")))
print(f"Python {python} (the lock needs {minimum} or newer){': too old' if too_old else ''}")

problems = 1 if too_old else 0
for name in needed:
    try:
        installed = importlib.metadata.version(name)
    except importlib.metadata.PackageNotFoundError:
        installed = None
    if installed == locked[name]:
        print(f"  {name:<11} {locked[name]:<8} ok")
    else:
        problems += 1
        print(f"  {name:<11} {locked[name]:<8} {'not installed' if installed is None else 'but ' + installed + ' is installed'}")

if problems:
    sys.exit(f"{problems} problem(s): stop here and run `uv sync --locked` in this project")
print(f"All {len(needed)} packages match uv.lock.")

Output

Python 3.14.8 (the lock needs 3.14.8 or newer)
  duckdb      1.5.6    ok
  numpy       2.5.3    ok
  pandas      3.0.6    ok
  polars      1.44.2   ok
  pyarrow     25.0.1   ok
All 5 packages match uv.lock.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_env.py

env/pyproject.toml

[project]
name = "sales-analysis"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14.8"
dependencies = [
    "duckdb==1.5.6",
    "numpy==2.5.3",
    "pandas==3.0.6",
    "polars==1.44.2",
    "pyarrow==25.0.1",
]

env/uv.lock

version = 1
revision = 5
requires-python = ">=3.14.8"
resolution-markers = [
    "sys_platform == 'win32'",
    "sys_platform == 'emscripten'",
    "sys_platform != 'emscripten' and sys_platform != 'win32'",
]

[[package]]
name = "duckdb"
version = "1.5.6"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "numpy"
version = "2.5.3"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "pandas"
version = "3.0.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "numpy" },
    { name = "python-dateutil" },
    { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
]

[[package]]
name = "polars"
version = "1.44.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "polars-runtime-32" },
]

[[package]]
name = "polars-runtime-32"
version = "1.44.2"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "pyarrow"
version = "25.0.1"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "six" },
]

[[package]]
name = "sales-analysis"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
    { name = "duckdb" },
    { name = "numpy" },
    { name = "pandas" },
    { name = "polars" },
    { name = "pyarrow" },
]

[package.metadata]
requires-dist = [
    { name = "duckdb", specifier = "==1.5.6" },
    { name = "numpy", specifier = "==2.5.3" },
    { name = "pandas", specifier = "==3.0.6" },
    { name = "polars", specifier = "==1.44.2" },
    { name = "pyarrow", specifier = "==25.0.1" },
]

[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "tzdata"
version = "2026.5"
source = { registry = "https://pypi.org/simple" }

Version note

Run in your browser, the check reports six problems and stops. Pyodide 314.0.7 contains CPython 3.14.2, older than the 3.14.8 the lock asks for, and none of the five packages is installed there. That is the check doing its job: this runner cannot run the project’s pandas, polars or DuckDB code, and the check says so before anything else runs.

In your browser, Pyodide 314.0.7 (CPython 3.14.2) prints (exit status 1):

Python 3.14.2 (the lock needs 3.14.8 or newer): too old
  duckdb      1.5.6    not installed
  numpy       2.5.3    not installed
  pandas      3.0.6    not installed
  polars      1.44.2   not installed
  pyarrow     25.0.1   not installed

Printed as an error (standard error):

6 problem(s): stop here and run `uv sync --locked` in this project

The same program in a colleague’s project, whose lock still pins pandas 2.3.3 and adds Matplotlib, run with this track’s environment:

The check stops a run in the wrong environment

older-project/check_env.py

# Stop an analysis before it runs in the wrong environment: compare what this Python has installed with the
# versions the project's uv.lock records. Run it first, or paste it into the first cell of a notebook.
import importlib.metadata
import platform
import sys
import tomllib

with open("uv.lock", "rb") as f:
    lock = tomllib.load(f)

locked = {p["name"]: p["version"] for p in lock["package"]}
project = next(p for p in lock["package"] if p["source"].get("virtual") == ".")
needed = sorted(dep["name"] for dep in project.get("dependencies", []))

python = platform.python_version()
minimum = lock["requires-python"].removeprefix(">=")
too_old = tuple(map(int, python.split("."))) < tuple(map(int, minimum.split(".")))
print(f"Python {python} (the lock needs {minimum} or newer){': too old' if too_old else ''}")

problems = 1 if too_old else 0
for name in needed:
    try:
        installed = importlib.metadata.version(name)
    except importlib.metadata.PackageNotFoundError:
        installed = None
    if installed == locked[name]:
        print(f"  {name:<11} {locked[name]:<8} ok")
    else:
        problems += 1
        print(f"  {name:<11} {locked[name]:<8} {'not installed' if installed is None else 'but ' + installed + ' is installed'}")

if problems:
    sys.exit(f"{problems} problem(s): stop here and run `uv sync --locked` in this project")
print(f"All {len(needed)} packages match uv.lock.")

Output (exit status 1)

Python 3.14.8 (the lock needs 3.14.8 or newer)
  matplotlib  3.11.2   not installed
  pandas      2.3.3    but 3.0.6 is installed

Printed as an error (standard error)

2 problem(s): stop here and run `uv sync --locked` in this project

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_env.py

older-project/pyproject.toml

[project]
name = "quarterly-report"
version = "0.1.0"
requires-python = ">=3.14.8"
dependencies = [
    "matplotlib==3.11.2",
    "pandas==2.3.3",
]

older-project/uv.lock

version = 1
revision = 5
requires-python = ">=3.14.8"

[[package]]
name = "contourpy"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "numpy" },
]

[[package]]
name = "cycler"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "fonttools"
version = "4.66.1"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "kiwisolver"
version = "1.5.1"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "matplotlib"
version = "3.11.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "contourpy" },
    { name = "cycler" },
    { name = "fonttools" },
    { name = "kiwisolver" },
    { name = "numpy" },
    { name = "packaging" },
    { name = "pillow" },
    { name = "pyparsing" },
    { name = "python-dateutil" },
]

[[package]]
name = "numpy"
version = "2.5.4"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "packaging"
version = "26.3"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "pandas"
version = "2.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "numpy" },
    { name = "python-dateutil" },
    { name = "pytz" },
    { name = "tzdata" },
]

[[package]]
name = "pillow"
version = "12.3.0"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "pyparsing"
version = "3.3.3"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "six" },
]

[[package]]
name = "pytz"
version = "2026.5"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "quarterly-report"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
    { name = "matplotlib" },
    { name = "pandas" },
]

[package.metadata]
requires-dist = [
    { name = "matplotlib", specifier = "==3.11.2" },
    { name = "pandas", specifier = "==2.3.3" },
]

[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "tzdata"
version = "2026.5"
source = { registry = "https://pypi.org/simple" }

Two problems, both caught before a single number was computed: Matplotlib is missing, and pandas is 3.0.6 where the project expects 2.3.3. Running that colleague’s report here would at best crash and at worst print different numbers without a word of warning. uv sync --locked inside their project folder builds the environment their lock describes, next to this one.

Scripts, Jupyter notebooks or marimo

The code of an analysis can live in three kinds of file, and each suits a different stage of the work.

A script is a plain .py file that runs from top to bottom every time. It is the easiest form to review, test, schedule and rerun, and it never holds state from an earlier run, but its tables and charts are not shown next to the code.

A Jupyter notebook is a .ipynb file: JSON that holds the cells, the output each cell printed and a count of the order in which they ran (notebook format). The cells run in a kernel that keeps every variable while the notebook is open, so you can run them in any order. That makes exploring quick and leaves the classic trap: the output on the page may come from cells run in another order, or from a cell that has since been edited or deleted. Before you trust or share a notebook, use JupyterLab’s “Restart Kernel and Run All Cells” (commands reference) and check that the numbers do not change.

A marimo notebook is stored as a plain Python file and can also run as a script. When you run a cell, marimo runs every cell that uses the variables it defines, and deleting a cell deletes its variables, so the page cannot quietly disagree with the code (running cells). One gap remains: marimo does not track changes made inside an object, such as rows.append(x) in another cell, so define and change a variable in the same cell.

Script, Jupyter notebook or marimo?
Criterion Script (.py)Jupyter (.ipynb)marimo (.py)
Runs in a fixed order Yes, top to bottomNo: any order, state kept between runsYes, by the order of dependencies
Shows results next to the code NoYes, saved in the fileYes, while open; exported on request
Reads well in a code review (diff) YesPoorly: JSON with outputs insideYes
Runs unattended, on a schedule YesWith extra toolsYes, as a script
When to choose Reports and pipelines that run again and againExploring, and sharing results with their outputsExploring with code that stays consistent and reviewable

Many analyses use two of them: a notebook while exploring, then a script once the steps are settled and the result has to be produced again every week. Whichever you use, the check from the section above goes first.

Where this track runs its examples

The output under each example is what it printed when it ran with Python 3.14.8 and the versions in the lock file above. A Run button runs an example in your browser with Pyodide 314.0.7, which contains CPython 3.14.2 and the standard library, so it appears only on examples that need nothing else.

TOML ↔ JSON Converter Paste a pyproject.toml or a uv.lock to check that its TOML is valid and see it as JSON. Python Online Compiler Try the standard-library examples of this lesson, such as the three sums, with nothing installed.

Exercise

Exercise · Easy · Python

Report what differs from the lock file

The lesson's check_env.py compares the versions in uv.lock with the versions this Python has installed. Write the comparison as a function you can test: problems(locked, installed) in problems.py.

Both arguments map package names to versions, such as {"pandas": "3.0.6"}. A package that is not installed is missing from installed. Return one message for every locked package that is not installed at its locked version, sorted by package name, in exactly these forms:

  • "pandas: locked 3.0.6, installed 2.3.3" when another version is installed;
  • "polars: locked 1.44.2, not installed" when it is missing.

Packages that are installed but not in the lock do not matter here. When everything matches, return an empty list.

Starter code · problems.py

def problems(locked, installed):
    """Messages for the locked packages whose installed version differs, sorted by package name.

    locked and installed map package names to versions, such as {"pandas": "3.0.6"};
    a package that is not installed is missing from installed.
    """
    # Replace this line with your code.
    return []
The sample tests · test_problems.py
from problems import problems

LOCKED = {"duckdb": "1.5.6", "numpy": "2.5.3", "pandas": "3.0.6", "polars": "1.44.2", "pyarrow": "25.0.1"}


def test_everything_matches():
    """returns an empty list when every locked version is installed"""
    assert problems(LOCKED, dict(LOCKED)) == []


def test_other_version_installed():
    """reports a package installed at another version"""
    installed = dict(LOCKED, pandas="2.3.3")
    assert problems(LOCKED, installed) == ["pandas: locked 3.0.6, installed 2.3.3"]


def test_missing_package():
    """reports a package that is not installed"""
    installed = {k: v for k, v in LOCKED.items() if k != "polars"}
    assert problems(LOCKED, installed) == ["polars: locked 1.44.2, not installed"]


def test_sorted_and_extras_ignored():
    """lists problems by package name and ignores packages the lock does not name"""
    installed = {"numpy": "2.5.3", "duckdb": "1.4.0", "pandas": "3.0.6", "polars": "1.44.2", "rich": "15.0.0"}
    assert problems(LOCKED, installed) == ["duckdb: locked 1.5.6, installed 1.4.0", "pyarrow: locked 25.0.1, not installed"]
A hint

Walk through sorted(locked), so the messages come out in name order. For each name, installed.get(name) gives the installed version, or None when the package is missing; compare it with locked[name] and append the message that fits. An f-string such as f"{name}: locked {locked[name]}, not installed" builds the text.

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.

Check yourself

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 Which files of a uv analysis project belong in version control?

    Choose every answer that is right.

    Show the answer to question 1

    Answer:

    • pyproject.toml
    • uv.lock
    • .python-version

    The three files describe the environment completely, so any computer can rebuild it with uv sync --locked. The .venv folder is that rebuilt environment: large, specific to one operating system, and recreated in seconds.

  2. Question 2 of 5 You change pandas==3.0.6 to pandas==3.0.5 in pyproject.toml but do not run uv lock. What does uv sync --locked do?

    Choose one answer.

    Show the answer to question 2

    Answer: It stops with an error, because the lock file no longer matches pyproject.toml

    --locked asserts that uv.lock will not change. A lock that no longer matches pyproject.toml would have to change, so uv refuses and tells you to run uv lock first. Without --locked, uv would update the lock quietly.

  3. Question 3 of 5 On Python 3.14, adding ten payments of 0.1 with a for loop gives 0.9999999999999999 and sum() gives 1.0. What did sum() print on Python 3.11?

    Choose one answer.

    Show the answer to question 3

    Answer: 0.9999999999999999, the same as the loop

    Before Python 3.12, sum() added floats from left to right, exactly like the loop, so it printed the same rounded result. Python 3.12 switched sum() of floats to a more accurate algorithm. Code that compared such a total with == changed its answer on the upgrade.

  4. Question 4 of 5 Your Jupyter notebook shows a revenue total that you computed after running its cells out of order. What should you do before you share it?

    Choose one answer.

    Show the answer to question 4

    Answer: Restart the kernel, run all cells from the top, and check that the total is unchanged

    The kernel keeps variables from every earlier run, so the saved output may depend on an order that the page does not show. Restarting and running every cell from the top is the only way to see what the notebook, as written, computes.

  5. Question 5 of 5 Where does a check like check_env.py belong?

    Choose one answer.

    Show the answer to question 5

    Answer: Before the analysis, so that a wrong environment stops the run before any number is computed

    The point of the check is that nothing gets computed in the wrong environment. uv run does sync a uv project's environment, but notebooks, other tools and other people's computers do not always go through uv, and the check costs a fraction of a second.

Interview questions

Fresher · What is a lock file, and how is it different from the dependency list in pyproject.toml?

The dependency list in pyproject.toml says what the project needs, often as ranges such as pandas>=3.0, and names only the packages you chose. A lock file such as uv.lock records the result of resolving that list: the exact version of every package that gets installed, the dependencies of your dependencies included, with the source and the hash of each file. Two computers that install from the same lock get the same environment, while two installs from pyproject.toml alone can pick different versions on different days. You edit pyproject.toml, by hand or with uv add; the tool writes the lock, and you commit both. uv sync --locked installs exactly what the lock says and fails when the lock no longer matches pyproject.toml.

Key takeaways

  • The environment, meaning the interpreter and the exact library versions, is part of an analysis: the same code can print different numbers in another one.
  • Give each analysis its own uv project: uv init, uv add with exact versions, uv run, and uv sync --locked on every other computer.
  • Commit pyproject.toml, uv.lock and .python-version; never .venv.
  • Check the installed versions against the lock before the analysis runs, and stop when they differ.
  • Scripts are for runs you repeat, notebooks for exploring; restart and run all cells before trusting a Jupyter notebook.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.