length = 8
width = 5
area = length * width
area40
After completing this compulsory primer, you will be able to:
int, float, str, bool, and None;This primer assumes no previous programming experience. You need only basic arithmetic. All accompanying code for this primer is original, runs locally with the Python standard library, and is located in source/code/primer01_execution.py.
The runtime is the program that actually executes the code. In this unit, that runtime is a local Python interpreter. A terminal is a text window in which we ask the system to run programs. Jupyter and Quarto use the same runtime when both are configured to use the same environment.
From the project root, check the runtime with the following commands. Type the commands themselves, not any prompt symbol displayed to their left.
python --version
python -c "print(2 + 3)"
The first command should report Python 3. The second asks Python to calculate 2 + 3 and then write 5 to the terminal. If python is not found, do not install packages at random. First follow the local setup guide, activate the course environment, and check the active version again.
Three preliminary questions prevent many irreproducible results:
A relative path such as source/code/primer01_execution.py is interpreted relative to the working directory. For this reason, the examples in this book are run from the project root.
An expression asks Python to produce a value. The number 4, the expression 2 + 3, and the call len("data") each produce a value. The = operator performs assignment: Python evaluates the right-hand side and then binds the result to the name on the left-hand side.
length = 8
width = 5
area = length * width
area40
The name area is not a permanent box. A later assignment can bind the same name to a new value. To check whether values are equal, use ==, not =.
area == 40True
Choose meaningful names, such as sample_count, rather than vague names such as x1, unless that short symbol is itself part of the mathematical model.
Common scalar values you will encounter first are:
| Type | Example | Initial meaning |
|---|---|---|
int |
8 |
an integer |
float |
0.01 |
an approximation to a real number |
str |
"test A" |
text |
bool |
True |
a truth value: true or false |
NoneType |
None |
no value yet, or no result |
count = 8
tolerance = 0.01
label = "test A"
passed = count > 5
note = None
type(count).__name__'int'
Call type(...) with the other names to check their types one at a time. Lists, tuples, loops, and comprehensions are deliberately left until P02; this primer does not need them yet.
None is different from 0, False, and the empty string "". It indicates that no value has been supplied. Do not use None as a number.
The basic arithmetic operators are +, -, *, /, //, %, and **. Division with / produces a float; // is floor division; % gives the remainder consistent with floor division; and ** is exponentiation.
sum_result = 7 + 2
difference = 7 - 2
product = 7 * 2
quotient = 7 / 2
floor_quotient = 7 // 2
remainder = 7 % 2
power = 7**2
power49
All seven names still hold scalar values. No collection has been created; lists and tuples are introduced in P02.
The comparisons ==, !=, <, <=, >, and >= produce bool values. The operators and, or, and not combine truth values. Use parentheses when the order of operations needs to be clear to the reader.
A function accepts arguments and may return a value. In round(3.14159, ndigits=3), 3.14159 is a positional argument and ndigits=3 is a keyword argument.
round(3.14159, ndigits=3)3.142
A module groups reusable names. Import the math module, then include the module name so that the function’s origin remains visible.
import math
radius = 3
circle_area_value = math.pi * radius**2
circle_area_value28.274333882308138
Use help(math.sqrt) in Python or math.sqrt? in a Jupyter cell to view local documentation. Help explains a function’s contract; it does not guarantee that the function is suitable for your question. Check the meaning of the arguments, units, domain, return value, and possible errors.
A Jupyter notebook has code cells and Markdown cells. Code cells are sent to the Python kernel; Markdown cells contain explanations, equations, and the connections between questions and results. Quarto uses fenced code blocks in .qmd files and can execute them when building HTML or PDF.
The source of a Quarto code block looks like this:
~~~{python}
value = 6 * 7
value
~~~
Running one cell does not automatically run all the cells above it. The kernel retains names while its process is alive. Cell execution numbers show the order in which cells actually ran; a cell’s position on the page shows only the source order. This distinction matters for reproducibility.
Python can compute a value without displaying it. In a notebook, the value of the last expression is usually passed to the display mechanism. Assignment usually does not display a representation of the value. print(...) writes text to standard output. In an ordinary script, a bare expression such as 2 + 3 is still evaluated but is not automatically printed.
computed_value = 2 + 3
display_text = f"2 + 3 = {computed_value}"
print(display_text)2 + 3 = 5
computed_value is an int with value 5. display_text is a str chosen for communication. An attractive display does not prove that a computation is correct, and a value in memory is not yet a saved artifact. If a result needs to be audited, save the data and metadata in specified files.
Quarto can capture cell output for inclusion in the reader. That output still needs interpretation: is it a value, diagnostic text, a figure, or an error message?
When Python fails, it displays a traceback. Read the last line first: it gives the error type and a short message. Then find the nearest frame that points to your file and line of code. The frames above it explain the chain of calls that brought execution there.
Traceback (most recent call last):
File "experiment.py", line 2, in <module>
result = data * 2
NameError: name 'data' is not defined
NameError means that a name has not been bound in the namespace being used. TypeError usually means that an operation received an unsuitable type of value. ZeroDivisionError indicates division by zero. SyntaxError means that the source could not be parsed as a Python program. The message is initial evidence, not a reason to delete lines at random.
In P01, run a failing example in a console or practice cell, read the traceback, and then correct its cause. The try/except syntax is taught in P02; the book’s supplied checking blocks may use it as scaffolding, but you are not yet expected to write that structure yourself. Do not hide failures throughout the program merely to make the output look green.
A .py file is script source that can be run again. From the project root, run the P01 companion:
python source/code/primer01_execution.py --output output/p01-results.json
The script computes the same example each time and writes a JSON record. It also prints one location message so that a person can tell which file was created. Open output/p01-results.json as text. The computed_value field stores the computed value, while display_text stores the text chosen for the reader. The runtime fields name the Python implementation and version that actually ran the script.
For now, simply understand that the terminal runs the file from beginning to end. Module structure and the main part of a script are discussed in P02.
Create five names: count=8, tolerance=0.01, label="test A", a boolean passed that checks whether count > 5, and note=None. Also calculate the floor quotient and remainder when dividing count by 3. Run the code.
Use // for the floor quotient, % for the remainder, and type(...) to check the types of the values.
Add the following checks below your code. The cell should finish without an AssertionError.
assert type(count) is int
assert type(tolerance) is float
assert type(label) is str
assert type(passed) is bool and passed is True
assert note is None
assert count // 3 == 2
assert count % 3 == 2Solution 2.1.
count = 8
tolerance = 0.01
label = "test A"
passed = count > 5
note = None
floor_quotient = count // 3
remainder = count % 3
assert floor_quotient == 2
assert remainder == 2Each name is bound to the value produced by its right-hand side. The comparison count > 5 produces a bool; None is not the number zero.
Import the math module, then calculate the area of a circle of radius 3 using circle_area from the P01 companion. Use local help to find the meaning of math.isclose, then compare the function result with math.pi * 3**2.
Import the companion with from source.code.primer01_execution import circle_area. Call help(math.isclose) in a separate console if the output is long.
import math
from source.code.primer01_execution import circle_area
exercise_area = circle_area(3)
assert math.isclose(exercise_area, math.pi * 9, rel_tol=0.0, abs_tol=1e-15)Solution 2.2.
import math
from source.code.primer01_execution import circle_area
exercise_radius = 3
exercise_area = circle_area(exercise_radius)
assert math.isclose(
exercise_area,
math.pi * exercise_radius**2,
rel_tol=0.0,
abs_tol=1e-15,
)The import provides a module or function name. The call supplies the argument 3 and returns a value. The isclose documentation explains the relative and absolute tolerances that form part of the check.
Call circle_area(-2), read the traceback, and state the error type and its final message. Then correct the input to 2 without changing the function merely to hide the error.
The last line names ValueError. Run the failure in a separate console. The following check is supplied scaffolding; its try/except syntax will be covered in P02.
from source.code.primer01_execution import circle_area
try:
circle_area(-2)
except ValueError as radius_error:
assert str(radius_error) == "radius must not be negative"
else:
raise AssertionError("negative input should have been rejected")
assert circle_area(2) > 0The type is ValueError and the message is radius must not be negative. The function rejects input outside its contract. The correct solution is to supply a nonnegative radius or correct the data source, not to remove the check.
Solution 2.3.
from source.code.primer01_execution import circle_area
valid_radius = 2
valid_area = circle_area(valid_radius)
assert valid_area > 0Run the P01 script from the terminal. In Python, call computation_and_display(8, 5). Identify the field that stores the computed result and the field that stores only display text. Explain why printing text is not the same as saving a JSON record.
The computed value has type int; the display has type str. print sends text to standard output, whereas the script writes bytes to the specified path. Dictionary indexing in the checking block is supplied scaffolding and will be explained in P02.
from source.code.primer01_execution import computation_and_display
display_record = computation_and_display(8, 5)
assert display_record["computed_value"] == 13
assert display_record["display_text"] == "8 + 5 = 13"
assert type(display_record["computed_value"]) is int
assert type(display_record["display_text"]) is strSolution 2.4.
from source.code.primer01_execution import computation_and_display
display_record = computation_and_display(8, 5)
value_computed = display_record["computed_value"]
text_displayed = display_record["display_text"]
assert value_computed == 13
assert text_displayed.endswith("= 13")Terminal text can disappear when the terminal closes, and it does not bind the result to a runtime version or parameters. The script’s JSON file is an artifact that can be read again, checked, and hashed. Both still need to be evaluated against the actual mathematical question.
Use the P01 notebook to demonstrate three facts: Cell B succeeds in an old kernel that has run Cell A; Cell B fails with NameError in a clean kernel; and the sequence A followed by B succeeds after Restart Kernel and Run All Cells. This is a compulsory mastery exercise.
Open source/notebooks/o002-p01-clean-kernel.ipynb. Follow the three marked states in the notebook and do not add unrecorded manual values. The following checking block is project-test scaffolding that you only need to run in P01; the lists, Path, attributes, keyword arguments, subprocess, and sys it uses are discussed in P02 or later project units.
from pathlib import Path
import subprocess
import sys
kernel_test = subprocess.run(
[
sys.executable,
"-m",
"unittest",
"tests.test_primer01.Primer01NotebookKernelTests",
],
cwd=Path.cwd(),
capture_output=True,
text=True,
)
assert kernel_test.returncode == 0, kernel_test.stdout + kernel_test.stderrassert "OK" in kernel_test.stderr
notebook_check_result = "actual kernel: fails out of order; clean run-all passes"
notebook_check_resultSolution 2.5.
'actual kernel: fails out of order; clean run-all passes'
The test starts a real Jupyter kernel. Cell B alone fails with NameError, whereas the complete notebook runs Cell A followed by Cell B in a new kernel and produces 14. The source order, not an old history of clicks, is the evidence that can be executed again.
int, float, str, bool, and None have different roles.