Field notesAUWENSearch
Explore Systems
code

Python: first script to useful software

Learn how programs store information, make decisions, repeat work, and handle files.

5 lessons published · Updated 2026-09-14

Before you begin

Install Python 3 from python.org. Open a terminal and run python3 --version (Windows: py -3 --version). Use a plain-text editor. Save each example in its own .py file; run it from that folder. No third-party packages are needed for these lessons.

Working toward: Build a command-line field-notes processor with validated input, tests, and a distributable package.

Read each explanation, run the example in your own lab, and attempt the exercise before opening its answer. Published lessons are ready to study; unfinished roadmap topics remain planned.

Validation: Lessons 1–4 retain their prior expected-output checks. Lesson 5 was executed on Linux with Python 3.12.14: normal, zero, invalid and missing arguments, --help, abbreviated-option rejection, and the exercise solution. Python 3.14 documentation was reviewed; 3.14 itself and Windows were not execution-tested.

1. Values, names, and output

A program is a sequence of instructions. A variable is a name referring to a value. Here nights and nightly_rate refer to integers; multiplication produces another integer. print sends text to your terminal. An f-string evaluates expressions inside braces.

nights = 7
nightly_rate = 80
total = nights * nightly_rate
print(f"Accommodation: {total} EUR")

Run it

python3 budget.py
# Windows: py -3 budget.py

What to expect

Accommodation: 560 EUR

Your turn

Add a daily food allowance of 25 EUR for seven days and print the combined budget.

Show answer and reasoning
food = 25 * nights
print(f"Total: {total + food} EUR")
# Total: 735 EUR

Watch for: "7" is text; 7 is a number. Multiplying text repeats it. Quotes matter, and capitalization distinguishes names.

Link to this lesson

2. Decisions and repeated work

A list holds an ordered collection. A for loop visits each item. if chooses a branch based on a Boolean condition. Indentation groups instructions: the print below belongs to the if, which belongs to the loop.

costs = [8, 24, 13, 31]
for cost in costs:
    if cost > 20:
        print(f"Review: {cost}")

What to expect

Review: 24, then Review: 31, on separate lines.

Your turn

Print only costs between 10 and 25 inclusive.

Show answer and reasoning
for cost in costs:
    if 10 <= cost <= 25:
        print(cost)
# 24 and 13

Watch for: An equals sign assigns a value. == compares values. Mixing tabs and spaces can break indentation.

Link to this lesson

3. Functions and checks

A function names a reusable calculation. Parameters receive inputs; return supplies a result to the caller. Keeping calculation separate from printing makes it easier to test. This example checks for a negative count before calculating.

def trip_total(days, daily):
    if days < 0:
        raise ValueError("days must be nonnegative")
    return days * daily

assert trip_total(3, 12) == 36
assert trip_total(0, 12) == 0
print(trip_total(7, 25))

What to expect

175; assertions produce no output when they pass.

Your turn

Allow a fixed transport charge as a third parameter, defaulting to zero. Check both default and explicit values.

Show answer and reasoning
def trip_total(days, daily, transport=0):
    if days < 0:
        raise ValueError("days must be nonnegative")
    return days * daily + transport

assert trip_total(3, 12) == 36
assert trip_total(3, 12, 5) == 41

Watch for: assert is useful for learning checks but can be disabled. Validate user input with explicit conditions and exceptions.

Link to this lesson

4. Read structured data

CSV gives each record a row and each field a column. DictReader uses the first row as field names. A with block closes the file after use. Convert the amount column to an integer before adding; this fixture stores whole euros.

import csv

with open("expenses.csv", newline="", encoding="utf-8") as source:
    rows = csv.DictReader(source)
    total = sum(int(row["euros"]) for row in rows)
print(total)

Run it

Create expenses.csv with these three lines:
item,euros
lunch,12
taxi,18

What to expect

30

Your turn

Add a coffee row for 4 euros. Then try a blank amount and identify the exception before deciding how the program should report it.

Show answer and reasoning
The total becomes 34. int("") raises ValueError. A useful next revision catches it and reports the row number; silently treating malformed amounts as zero hides bad data.

Watch for: File paths are relative to your terminal’s working directory. Do not install packages into a system Python just to solve a missing-file error.

Link to this lesson

5. Accept command-line input without trusting it

Before this lesson: Complete values, decisions and functions (lessons 1–3); lesson 4's CSV work is useful context but not required. Use Python 3.12 or newer in a new, empty lab folder. No packages, files of personal data, or network access are needed. Outcome: build a CLI that explains its inputs, rejects bad values and returns a predictable result.

A command-line interface (CLI) receives text after a program name. A positional argument is identified by its location; an option has a name such as --daily-cents. Parsing identifies these pieces. Validation decides whether their values make sense for this program. Integer conversion alone accepts negative numbers, so it does not enforce our nonnegative-budget rule.

The contract here is explicit: days and daily cents must be whole numbers at least zero. Money is represented as integer cents, not floating-point euros. Our examples use EUR; this is not a currency converter. Zero days and a zero rate are legitimate boundary cases. A missing required argument is different from an explicitly supplied zero.

argparse builds help, converts text, and reports usage errors. The type function receives one text value. int may raise ValueError if conversion fails; try/except catches just that failure and raises a clearer ArgumentTypeError. A second check rejects negatives. The parser calls the converter before handing a Namespace (a small object of named values) to the calculation. Attributes such as args.daily_cents correspond to option names with hyphens changed to underscores.

Save the complete program below as budget_cli.py. The main function keeps parsing and printing together; the __name__ guard runs it when this file is executed, but not when it is imported. allow_abbrev=False requires complete long option names, so later additions cannot change the meaning of an abbreviated command. Successful output belongs on stdout; argparse sends invalid-input diagnostics to stderr and exits with status 2. --help exits successfully without calculating.

import argparse

def nonnegative_int(text):
    try:
        value = int(text)
    except ValueError:
        raise argparse.ArgumentTypeError("use a whole number") from None
    if value < 0:
        raise argparse.ArgumentTypeError("must be zero or greater")
    return value

def main():
    parser = argparse.ArgumentParser(
        description="Estimate a daily budget in integer EUR cents.",
        allow_abbrev=False,
    )
    parser.add_argument("days", type=nonnegative_int, help="number of days")
    parser.add_argument(
        "--daily-cents", type=nonnegative_int, required=True,
        help="daily amount in EUR cents",
    )
    args = parser.parse_args()
    total = args.days * args.daily_cents
    print(f"Total: {total} EUR cents")

if __name__ == "__main__":
    main()

Run it

python3 budget_cli.py 7 --daily-cents 2500
python3 budget_cli.py 0 --daily-cents 2500
python3 budget_cli.py --help
python3 budget_cli.py -1 --daily-cents 2500
python3 budget_cli.py 7 --daily-cents tea
python3 budget_cli.py 7
# Windows: replace python3 with py -3.
# Observe a command's exit status immediately afterward:
# Bash: echo $?
# PowerShell: $LASTEXITCODE
# CMD: echo %ERRORLEVEL%

What to expect

The first two commands print Total: 17500 EUR cents and Total: 0 EUR cents; each exits 0. Help lists days and --daily-cents and exits 0. Negative days produce 'must be zero or greater'; tea produces 'use a whole number'; the final command reports the required --daily-cents option. These three failures exit 2 and print no total. Help layout and color can vary: Python 3.14 introduced color and suggestion settings, which this cross-version example does not require.

Your turn

Add --transport-cents, using the same validation, with a default of 0. Include it once in the total, not once per day. Before running, predict results for (a) 3 days at 1200 with no transport, (b) the same plus 500 transport, (c) 0 days plus 500 transport, and (d) -1 transport. Then try 1.5 days and --daily 1200. Explain why both should fail.

Show answer and reasoning
# Add before args = parser.parse_args():
parser.add_argument(
    "--transport-cents", type=nonnegative_int, default=0,
    help="one-time transport amount in EUR cents (default: 0)",
)
# Replace the total calculation:
total = args.days * args.daily_cents + args.transport_cents

# Predictions:
# (a) 3600; (b) 4100; (c) 500 EUR cents, all status 0.
# (d) status 2, no total: the shared converter rejects negatives.
# 1.5 is not an integer; --daily is not the full option name.
# The fixed charge is added after multiplication. Zero days does
# not waive it: that is the contract, not a side effect of parsing.
# Ready to move on: explain each failure and add a validated
# --entry-cents fixed charge without looking at this answer.

Watch for: Do not use eval to turn input into a number or assert to enforce this contract. Do not catch every exception and replace it with zero: that can make broken data look valid. This CLI validates its own boundary; if a calculation becomes a reusable public function later, decide and test that function's contract too. The accepted integer syntax is Python int's syntax, not a digits-only policy.

Lesson references

Link to this lesson

Path to advanced

In-progress stages identify the lessons already published. All other listed topics remain planned. Each addition needs teaching, a reproducible lab, failure cases, and a checkpoint before the capstone.

  1. Reliable scripts

    Published: validated command-line arguments (lesson 5). Planned: broader exception handling, pathlib, logging, virtual environments, and automated test suites.

    IN PROGRESS · PUBLISHED LESSONS ABOVE
  2. Working with data

    JSON, HTTP timeouts, SQLite, Unicode, generators, and type hints.

    PLANNED
  3. Software engineering

    Modules, packaging, dependency management, CI, profiling, and concurrency.

    PLANNED
  4. Capstone

    Deliver a documented corpus-cleaning CLI with tests, sample data, and reproducible installation.

    PLANNED

References

Original AUWEN lessons, with upstream documentation for further study and version checks.

All learning paths and update notes →