Agile System SoftwareLearn

Workshop · Python · Beginner

PY050 Python from zero

Python taught the way working programmers use it: a small, honest program in every step, run from the terminal in your own VS Code seat, with the AI tutor beside you. Forty short steps across eight sessions take you from printing a line to a tested command-line expense tracker that reads and writes real files, and every step ends with a check the machine runs on your own code.

Open Coding Tutor at codingtutor.agilesystemsoftware.com, pick PY050 from the course button in the chat and press Tutor me. Every lesson, demo and hands-on step is also published below.

  • LevelBeginner
  • Durationself-paced ~20–25 h
  • Size8 sessions · 40 mini steps · ~320 min of lessons
  • EnvironmentA VS Code seat in your browser with Python 3.13, pytest and basedpyright, ready and offline; nothing to install
  • PrerequisitesNone. If you have coded in another language, take the placement step 8.5 or start at PY100.
  • CertificateAgile System Software Certificate — Python from zero (PY050)

Who it is for

Adults and teens who have never programmed. You can type, you can follow a recipe, and you want to make a computer do something useful by the end of the first hour. No maths beyond percentages.

Where it leads. JV100 Java I · DN100 C# I: the working programmer’s toolkit · PY100 Python I · RS100 Rust I

What you will be able to do

  • Run a Python file from the terminal, read its output and read any error message back to the line it names
  • Hold text, whole numbers, decimals and money in the right type, and explain why 0.1 + 0.2 is not 0.3
  • Format a column of numbers with f-strings so a human can read it
  • Choose with if/elif/else and repeat with for and while, without an off-by-one
  • Keep things in lists and dictionaries, walk them, sort them and count them
  • Write a function with inputs and a return value, and test it with pytest
  • Read and write text and CSV files, closing them properly, and survive a missing file
  • Build a small command-line program with arguments and an exit-code contract

Syllabus

Every session ends in a lab the machine checks on your own code. Open a session's mini steps to read each lesson, demo and hands-on task; the badge shows how far the engine can verify it: checked · T3 a check runs over your files, proven · T4 the grader also tries inputs you did not see, tutored · T0 done on your word. Each step has its own link (for example #s1-1).

Session 1 · Your first programs

Five steps that make the seat yours. What a Python program is: a text file the interpreter reads from the top. The rhythm of the course — run, read, change, run again. Printing text and numbers, and the difference between the two. Reading an error message back to the line it names instead of guessing. Live in the seat: a file run from the terminal, a deliberate typo and its traceback, three ways to break print and what each one says, and the first saved program with a comment explaining what it does.

Lab Write greet.py: it prints a three-line banner, a sentence that uses a number, and a line built from two pieces of text joined together. It must run clean on the first try after you have read your own code once. Session 1 checkpoint.

5 mini steps · ~39 min
  1. 1.1Run, read, change8 minchecked · T3

    Predict

    A file hello.py contains one line: print("Hello"). What appears when you run python3 hello.py?

    Lesson

    A Python program is a plain text file; python3 reads it from the first line to the last and does what each line says. You will learn everything in one rhythm: run the file, read what came out, change one thing, run it again.

    Demo

    In the seat the tutor opens python/py050/demo/hello_demo.py, runs python3 hello_demo.py in the terminal, points at the output, changes the word inside the quotes, runs again, and shows that the terminal keeps the old output above the new.

    On screen

    Explorer → python/py050/demo/hello_demo.py → Terminal → python3 hello_demo.py → edit the string → run again.

    Hands-on

    Create python/py050/s1/hello.py with one print line of your own words (not Hello), run it, then add a second print line and run it again so the terminal shows both runs.

    Explain

    In one sentence: what did python3 do with your file?

    Check

    hello.py prints two lines of your own — checked (T3): seat_file seat_run

    #s1-1

  2. 1.2Text and numbers are different things7 minchecked · T3

    Predict

    What does print(2 + 3) print, and what does print("2" + "3") print?

    Lesson

    Python keeps text (str) and numbers (int, float) as different kinds of value and the same symbol does different work on each: + adds numbers but joins text. type() tells you what you have.

    Demo

    The tutor types five expressions in the Python REPL in the terminal — 2 + 3, "2" + "3", "2" * 3, 7 / 2, 7 // 2 — and reads each result aloud, then calls type() on two of them.

    Hands-on

    In python/py050/s1/types.py print four lines: the sum 40 + 2, the text "40" + "2", the text "ab" * 3, and type(42). Run it and compare with your predictions.

    Explain

    Why did "40" + "2" not give 42?

    Check

    types.py prints 42, 402, ababab and the int type — checked (T3): seat_run

    #s1-2

  3. 1.3Reading an error message8 minchecked · T3

    Predict

    A file has print("Hi" on line 3 with the closing bracket missing. Which line will the error message name?

    Lesson

    An error message is a map, not a verdict. The last line says what kind of error; the lines above say where. Go to the file and line it names first, read that line slowly, then think about the reason.

    Demo

    The tutor breaks python/py050/demo/broken_demo.py three ways in turn — a missing bracket, a misspelt print, a number added to text — runs each, and reads the traceback from the bottom up, pointing at the file:line each time.

    Hands-on

    Copy python/py050/samples/s1/oops.py to python/py050/s1/fixed.py. It has three errors. Fix them one at a time by reading the message, until it prints the two lines it was meant to print: "Total: 12" and "Done".

    Explain

    Which line of a traceback do you read first, and why?

    Check

    fixed.py runs clean and prints Total: 12 and Done — checked (T3): seat_run

    #s1-3

  4. 1.4Comments, names and the shape of a file7 minchecked · T3

    Predict

    After price = 5 and then price = 7, what does print(price) show?

    Lesson

    A variable is a name you give a value so you can use it again or replace it. A comment starts with # and is for people, not Python. A tidy file has a one-line comment at the top, names that say what they hold, and one idea per line.

    Demo

    The tutor writes python/py050/demo/names_demo.py: a comment, three variables (item, qty, price), a print that uses all three, then renames a variable badly (x) and well (unit_price) to show how the second reads a month later.

    Hands-on

    Write python/py050/s1/receipt_line.py with a header comment and variables item (text), qty (whole number) and unit_price (decimal); print one line like "3 x Notebook at 2.5" built from them.

    Explain

    Why is a comment not an instruction?

    Check

    receipt_line.py uses three named variables and a comment — checked (T3): seat_file seat_run

    #s1-4

  5. 1.5Lab: the greeting banner9 minchecked · T3

    Predict

    print("Py" + "thon", 3) prints what?

    Lesson

    Everything from this session in one small program: prints, text joined with +, a number used in a sentence, a header comment, and the discipline of reading your own file once before you run it.

    Demo

    The tutor builds python/py050/demo/banner_demo.py live: three banner lines of = and a title, a sentence with a number, a line from two joined strings; runs it; then shows the lab's check output so the learner knows what the grader reads.

    Hands-on

    Write python/py050/s1/greet.py: a comment on line 1; a banner of exactly three lines (a line of = signs, a title line, a line of = signs); one sentence that includes the number 5 computed as 2 + 3; and one line made by joining two strings with +. Run it clean on the first try.

    Explain

    Name one thing you now check in a file before you run it.

    Check

    greet.py: three-line banner, a computed 5, a joined line — checked (T3): seat_file seat_run

    #s1-5

Session 2 · Data: text, numbers and money

Five steps on the values a program holds. Text as a sequence of characters you can measure, cut and search. Whole numbers that are exact and decimals that are not — why 0.1 + 0.2 is not 0.3 in any binary language. Converting between text and numbers on purpose, and the error you get when the text is not a number. The flagship step: what survives when a number goes into a differently shaped home — int() drops the fraction, round() rounds, Decimal keeps the digits you typed — and why money is held as cents or Decimal, never as float. Live in the seat: a string sliced three ways, 0.1 + 0.2 printed to twenty places, int("4.5") failing, and the same 12345.67 pushed through four conversions.

Lab Write receipt.py: three fixed items with quantities and unit prices held in cents, a line per item and a total in dollars and cents, computed without a single float. The printed total must be exactly 47.25. Session 2 checkpoint.

5 mini steps · ~40 min
  1. 2.1Text you can measure and cut7 minchecked · T3

    Predict

    word = "notebook". What is word[0:4]?

    Lesson

    A string is a row of characters with positions starting at 0. len() measures it, square brackets pick a character or a slice, and methods such as .upper(), .strip() and .replace() give you a changed copy, never the original.

    Demo

    The tutor opens python/py050/demo/strings_demo.py and prints len("notebook"), "notebook"[0], "notebook"[-1], "notebook"[0:4], " hi ".strip() and "a-b-c".replace("-", "+"), reading each result as it appears.

    Hands-on

    Write python/py050/s2/cut.py: set title = "agile system software" and print its length, its first five characters, the last word (slice from position 13), and the whole title in upper case — four lines in that order.

    Explain

    Why does title.upper() not change title?

    Check

    cut.py prints 21, agile, software, AGILE SYSTEM SOFTWARE — checked (T3): seat_run

    #s2-1

  2. 2.2Whole numbers are exact, decimals are not8 minchecked · T3

    Predict

    What does print(0.1 + 0.2 == 0.3) show?

    Lesson

    int holds whole numbers exactly, as large as you like. float holds decimals in binary, so most decimal fractions are approximations. Use // for whole division and % for the remainder; / always gives a float.

    Demo

    In python/py050/demo/floats_demo.py the tutor prints 0.1 + 0.2, then the same with twenty decimal places using format, then 7 / 2, 7 // 2, 7 % 2 and 2 ** 10, pausing on the surprising one.

    Hands-on

    Write python/py050/s2/exact.py that prints 0.1 + 0.2, then 17 // 5, then 17 % 5, then 2 ** 8 — one per line. Look at the first line and say out loud why it is not 0.3.

    Explain

    In one sentence, why can a float not hold 0.1 exactly?

    Check

    exact.py shows the float surprise and three exact results — checked (T3): seat_run

    #s2-2

  3. 2.3Converting on purpose7 minchecked · T3

    Predict

    What happens with int("4.5")?

    Lesson

    int(), float() and str() convert between types when you ask. Text that is not a valid number raises ValueError, and the message names the text it refused. Python never converts silently between text and numbers.

    Demo

    The tutor runs python/py050/demo/convert_demo.py: int("42") + 1, str(42) + "1", float("2.50"), then int("4.5") to show the ValueError and read its last line aloud.

    Hands-on

    Write python/py050/s2/convert.py: qty_text = "12" and price_text = "2.50"; print the product of the two converted numbers (30.0), then print str(int(qty_text) * 2) + " items" (24 items). Then add a third line that converts "ten" with int(), run it, and read the error.

    Explain

    What did the ValueError message tell you?

    Check

    convert.py prints 30.0 and 24 items, then fails on int("ten") — checked (T3): seat_run seat_file

    #s2-3

  4. 2.4What survives: int, round and Decimal9 minchecked · T3

    Predict

    gross = 12345.67. What does print(int(gross)) show?

    Lesson

    When a number moves into a different shape, the shape decides what survives. int() truncates toward zero, round() rounds to the places you name, and Decimal keeps exactly the digits you typed. Money lives in Decimal or in integer cents, never in float.

    Demo

    In python/py050/demo/shapes_demo.py the tutor pushes 12345.67 through int(), round(..., 1), round(..., -2) and Decimal("12345.67").quantize(Decimal("0.1")), then adds Decimal("0.10") ten times and shows it equals Decimal("1.00").

    Hands-on

    Write python/py050/s2/shapes.py: from decimal import Decimal; amount = Decimal("12345.67"); print int(amount), round(amount, 1), amount.quantize(Decimal("1")), and the sum of Decimal("0.10") added ten times — four lines.

    Explain

    Why is Decimal, not float, used for money here?

    Check

    shapes.py prints 12345, 12345.7, 12346, 1.00 — checked (T3): seat_file seat_run

    #s2-4

  5. 2.5Lab: the receipt in cents9 minchecked · T3

    Predict

    total_cents = 4725. What does print(total_cents // 100, total_cents % 100) show?

    Lesson

    Integer cents make money arithmetic exact with nothing but int. Multiply quantity by unit cents, add the lines, then split the total into dollars and cents with // and % only when you print.

    Demo

    The tutor builds python/py050/demo/receipt_demo.py with two items, computes line totals in cents, prints each line and the total as dollars.cents, then deliberately changes a price to a float to show 0.1-style noise creep in.

    Hands-on

    Write python/py050/s2/receipt.py with three items: Notebook 3 × 250 cents, Pen 5 × 125 cents, Folder 2 × 1700 cents. Print one line per item ending in its line total in cents, then "Total: 47.25" built with // and %, zero-padding the cents. No float anywhere.

    Explain

    Why did you keep cents until the final print?

    Check

    receipt.py totals 47.25 from integer cents — checked (T3): seat_file seat_run craft

    #s2-5

Session 3 · Computing and formatting

Five steps on getting numbers right and making them readable. Arithmetic and precedence, with the brackets that make intent obvious. f-strings as the one way to build output text, with expressions inside the braces. Widths, alignment and thousands separators so a column of money lines up. Rounding on purpose: Python's round() rounds half to even, Decimal.quantize rounds the way a shop does, and you choose. Live in the seat: a percentage computed three ways with three different answers, a table of five prices aligned to the cent, and round(2.5) printing 2.

Lab Write tip.py: it takes a bill amount from the command line, computes a 15% tip rounded half up to the cent with Decimal, and prints the bill, tip and total as three aligned lines. Session 3 checkpoint.

5 mini steps · ~39 min
  1. 3.1Arithmetic and precedence7 minchecked · T3

    Predict

    What is 100 + 50 * 2 / 100?

    Lesson

    Python follows the arithmetic you learned at school: brackets, then powers, then multiply and divide, then add and subtract, left to right. When a formula matters, add brackets even when they are not needed; they tell the reader what you meant.

    Demo

    In python/py050/demo/precedence_demo.py the tutor computes a 12% increase on 250 three ways — 250 + 250 * 12 / 100, (250 + 250) * 12 / 100 and 250 * (1 + 12 / 100) — and shows which two agree and why.

    Hands-on

    Write python/py050/s3/percent.py that prints the price 250 increased by 12% (280.0), then the price 250 after a 12% discount (220.0), then what percentage 45 is of 180 (25.0) — each with brackets that show the intent.

    Explain

    Why add brackets a computer does not need?

    Check

    percent.py prints 280.0, 220.0, 25.0 — checked (T3): seat_run

    #s3-1

  2. 3.2f-strings: text with values inside7 minchecked · T3

    Predict

    qty = 3; print(f"{qty} x {qty * 2}") shows?

    Lesson

    An f-string is text with holes; anything inside the braces is evaluated and inserted. It replaces + and str() for building output, keeps the sentence readable, and after a colon you can say how the value should look.

    Demo

    The tutor rewrites python/py050/demo/receipt_demo.py from session 2 using f-strings, then shows {price:.2f}, {qty:>3} and {name:<10} one at a time with the output beside each change.

    Hands-on

    Write python/py050/s3/fstring.py with name = "Folder", qty = 2 and cents = 3400; print f"{qty} x {name}" and then f"Line total: {cents / 100:.2f}" — two lines: "2 x Folder" and "Line total: 34.00".

    Explain

    What does :.2f after a value do?

    Check

    fstring.py prints two formatted lines — checked (T3): seat_file seat_run

    #s3-2

  3. 3.3Columns that line up8 minchecked · T3

    Predict

    print(f"[{1234567:,}]") shows?

    Lesson

    Inside the braces, after the colon: a width pads to that many characters, < > and ^ align left, right and centre, a comma adds thousands separators, and .2f fixes two decimals. Right-align numbers and left-align names and a table reads itself.

    Demo

    In python/py050/demo/columns_demo.py the tutor prints five products with {name:<12}{qty:>4}{price:>10,.2f}, first without widths (a ragged mess), then with them, and shows how changing one width shifts the whole column.

    Hands-on

    Write python/py050/s3/table.py that prints a header and three rows for Notebook 3 at 2.50, Pen 12 at 1.25 and Desk 1 at 1250.00 using widths <10 for the name, >4 for qty and >10,.2f for price — the Desk row must show 1,250.00.

    Explain

    Why right-align numbers and left-align names?

    Check

    table.py prints an aligned table with 1,250.00 — checked (T3): seat_run

    #s3-3

  4. 3.4Rounding on purpose8 minchecked · T3

    Predict

    What does print(round(2.5), round(3.5)) show?

    Lesson

    round() rounds half to even, which is right for statistics and wrong for a till. Decimal's quantize with ROUND_HALF_UP rounds the way customers expect. Round once, at the end, to the places you name, and say which rule you used.

    Demo

    The tutor runs python/py050/demo/rounding_demo.py: round(2.5), round(0.125, 2), then Decimal("2.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP) and the same for "0.125" to two places, laying the four answers side by side.

    Hands-on

    Write python/py050/s3/round_up.py: import Decimal and ROUND_HALF_UP; for the three amounts "2.345", "2.355" and "10.005" print each quantized to Decimal("0.01") with ROUND_HALF_UP, one per line.

    Explain

    When is half-to-even the right rule?

    Check

    round_up.py prints 2.35, 2.36, 10.01 — checked (T3): seat_file seat_run

    #s3-4

  5. 3.5Lab: the tip calculator9 minchecked · T3

    Predict

    A 15% tip on 48.30, rounded half up to the cent, is?

    Lesson

    One program that uses everything in this session: a bill read from the command line (one line you will fully understand in session 8), Decimal arithmetic, half-up rounding to the cent, and three aligned output lines.

    Demo

    The tutor writes python/py050/demo/tip_demo.py: bill = Decimal(sys.argv[1]), tip = (bill * Decimal("0.15")).quantize(...), total = bill + tip, then f-strings with {label:<6}{value:>10.2f}; runs it with 48.30 and with 100.

    Hands-on

    Write python/py050/s3/tip.py: take the bill from sys.argv[1] as a Decimal, compute a 15% tip quantized half up to 0.01, and print three lines "Bill", "Tip" and "Total" each padded so the amounts right-align in a 10-wide column. With 48.30 the tip line shows 7.25 and the total 55.55.

    Explain

    Why quantize the tip before adding it to the bill?

    Check

    tip.py 48.30 → Bill 48.30, Tip 7.25, Total 55.55 — checked (T3): seat_file seat_run

    #s3-5

Session 4 · Choices and repetition

Five steps on control flow. if/else as a fork in the road, with the indented block as the thing Python actually runs. elif for a ladder of cases, comparisons that must compare like with like, and and/or/not combining conditions. for over a range to repeat a known number of times; while for as long as something holds, and the off-by-one that bites everyone once. Live in the seat: a grade ladder tested at every boundary, a loop that prints the twelve-times table, a while loop that runs one time too many and the fix, and a quiz that scores its answers.

Lab Write quiz.py: three fixed questions with known answers, the learner's answers taken from the command line, a point for each correct answer compared case-insensitively, and a final score line with a verdict. Session 4 checkpoint.

5 mini steps · ~39 min
  1. 4.1A fork in the road: if and else7 minchecked · T3

    Predict

    age = 17. After if age >= 18: print("adult") else: print("minor"), what prints?

    Lesson

    if tests a condition that is True or False and runs its indented block only when it is True; else runs otherwise. The colon and the indentation are the syntax: everything indented under the if belongs to it.

    Demo

    In python/py050/demo/ifelse_demo.py the tutor writes a temperature check, runs it with three values, then un-indents a line to show it now runs every time regardless of the condition.

    Hands-on

    Write python/py050/s4/stock.py: qty = 3; if qty is 0 print "Out of stock", otherwise print "In stock: 3" using an f-string. Then change qty to 0, run again, and set it back to 3.

    Explain

    How does Python know which lines belong to the if?

    Check

    stock.py branches on qty — checked (T3): seat_file seat_run

    #s4-1

  2. 4.2Ladders and combined conditions8 minchecked · T3

    Predict

    score = 90 in a ladder: if score >= 90 "A", elif score >= 80 "B", else "C". What prints?

    Lesson

    elif adds rungs to a ladder; the first True rung runs and the rest are skipped, so order matters. and, or and not combine conditions, and comparing text with a number is a bug the comparison will not hide for long.

    Demo

    The tutor builds the grade ladder in python/py050/demo/ladder_demo.py, tests 90, 89, 80 and 79 at the boundaries, then reverses two rungs to show every score become a C.

    Hands-on

    Write python/py050/s4/shipping.py: weight = 2.5 and express = True. Print "Free" if weight < 1 and not express, "Express" if express, else "Standard". Then print a second line for weight 0.5 and express False using the same ladder copied below.

    Explain

    Why does the order of elif rungs matter?

    Check

    shipping.py prints Express then Free — checked (T3): seat_file seat_run

    #s4-2

  3. 4.3for and range: a known number of times7 minchecked · T3

    Predict

    How many lines does for i in range(3): print(i) produce, and what is the last one?

    Lesson

    for repeats its block once per item; range(n) gives the numbers 0 up to but not including n, and range(a, b) from a up to but not including b. The loop variable takes each value in turn.

    Demo

    In python/py050/demo/for_demo.py the tutor prints range(5), then range(1, 6), then the twelve-times table with an f-string, pausing on the first and last line to show where range stops.

    Hands-on

    Write python/py050/s4/table7.py that prints the seven-times table from 1 x 7 = 7 to 10 x 7 = 70, one line each, in the exact form "3 x 7 = 21".

    Explain

    Why does range(10) not include 10?

    Check

    table7.py prints 10 lines from 1 x 7 to 10 x 7 — checked (T3): seat_file seat_run

    #s4-3

  4. 4.4while and the off-by-one8 minchecked · T3

    Predict

    n = 0; while n < 3: n = n + 1; then print(n). What prints?

    Lesson

    while repeats as long as its condition holds and checks it before every run. Something in the body must move the condition toward False, or the loop never ends. Off-by-one errors live at the boundary: test the first and last value by hand.

    Demo

    The tutor writes a savings loop in python/py050/demo/while_demo.py — add 50 until the balance reaches 300 and count the months — gets 7 instead of 6 with <=, prints the balance each pass, and fixes the condition.

    Hands-on

    Write python/py050/s4/double.py: balance = 1000; count the years until a 7% yearly increase makes the balance at least 2000, using a while loop; print "Years: 11" and the final balance to two decimals.

    Explain

    How did you check for an off-by-one?

    Check

    double.py counts 11 years to double at 7% — checked (T3): seat_file seat_run

    #s4-4

  5. 4.5Lab: the quiz9 minchecked · T3

    Predict

    "Paris".lower() == "paris" is?

    Lesson

    The quiz joins a loop with a choice: for each question compare the given answer with the right one, ignoring case, and add to a score. A final ladder turns the score into a verdict.

    Demo

    The tutor builds python/py050/demo/quiz_demo.py with two questions, answers from sys.argv[1:], a for over range(len(...)), .lower() on both sides, and a score line; runs it right, wrong and with shouted answers.

    Hands-on

    Write python/py050/s4/quiz.py with three questions whose answers are 8, paris and python. Read three answers from sys.argv[1:], score them case-insensitively, print "Correct" or "Wrong" per question, then "Score: 3/3" and "Perfect" for 3, "Good" for 2, else "Keep practising".

    Explain

    Why compare lower-cased copies instead of the originals?

    Check

    quiz.py scores 8 PARIS python as 3/3 Perfect and 8 rome java as 1/3 — checked (T3): seat_file seat_run seat_run

    #s4-5

Session 5 · Lists and dictionaries

Five steps on holding many things at once. A list as an ordered row you index, append to and walk. Walking with for, with enumerate when you need the position, and the in test. A dictionary as a lookup from key to value, how to add, read with a default and loop over its items. Sorting with sorted() and a key, counting with a dictionary, and the difference between a list of names and a list of records. Live in the seat: a list grown from empty, a dictionary of prices with a missing key and the KeyError it raises, a shopping list sorted three ways, and category counts built in a loop.

Lab Write shopping.py: a fixed list of items each with a category and a price in cents; print the items sorted by name, the number of items per category, and the most expensive item. Session 5 checkpoint.

5 mini steps · ~39 min
  1. 5.1A list of things7 minchecked · T3

    Predict

    items = ["pen", "ink"]; items.append("pad"); print(items[2], len(items))

    Lesson

    A list holds values in order. Square brackets make one, [i] reads position i (from 0), .append() adds to the end, len() counts, and in asks whether a value is present. Lists change in place, unlike strings.

    Demo

    In python/py050/demo/list_demo.py the tutor starts from [] and appends three items, prints the list, items[0], items[-1], len(items), "ink" in items, then reads items[5] to show the IndexError.

    Hands-on

    Write python/py050/s5/cart.py: start with an empty list, append "pen", "ink" and "pad", then print the list, its length, its last item, and whether "ink" is in it — four lines.

    Explain

    What is the difference between items[-1] and items[len(items)]?

    Check

    cart.py builds and inspects a three-item list — checked (T3): seat_file seat_run

    #s5-1

  2. 5.2Walking a list7 minchecked · T3

    Predict

    for i, name in enumerate(["a", "b"], start=1): print(i, name). The first line is?

    Lesson

    for item in items visits each value in order; enumerate(items) gives the position too. Build a total by starting a variable at 0 before the loop and adding inside it, and never change a list while you are walking it.

    Demo

    The tutor sums a list of cents in python/py050/demo/walk_demo.py, then prints a numbered list with enumerate, then shows the classic mistake of writing total = price inside the loop instead of total += price.

    Hands-on

    Write python/py050/s5/numbered.py: prices = [250, 125, 1700]; print a numbered line per price starting at 1 in the form "1. 250", then "Total: 2075".

    Explain

    Why must the total start at 0 before the loop?

    Check

    numbered.py lists three prices and totals 2075 — checked (T3): seat_file seat_run

    #s5-2

  3. 5.3Dictionaries: looking things up8 minchecked · T3

    Predict

    prices = {"pen": 125}; print(prices.get("ink", 0)) shows?

    Lesson

    A dictionary maps keys to values: curly braces, key: value pairs, [key] to read or set, .get(key, default) to read without an error, and for key, value in d.items() to walk it. Keys are unique; setting one again replaces its value.

    Demo

    In python/py050/demo/dict_demo.py the tutor builds a price dictionary, reads two keys, reads a missing one with [ ] to show the KeyError, then with .get, then walks it with .items() printing name and price.

    Hands-on

    Write python/py050/s5/prices.py: a dictionary of pen 125, ink 450, pad 300. Print the price of ink, then prices.get("desk", 0), then set pad to 350 and print one "name: price" line per item in insertion order.

    Explain

    When would you use [key] rather than .get?

    Check

    prices.py reads, defaults, updates and walks a dictionary — checked (T3): seat_file seat_run

    #s5-3

  4. 5.4Sorting and counting8 minchecked · T3

    Predict

    sorted(["pad", "Pen", "ink"]) gives?

    Lesson

    sorted() returns a new sorted list and leaves the original alone; key= names a function that picks what to sort by, reverse=True flips it. Counting by category is a dictionary whose values start at 0 and grow inside a loop.

    Demo

    The tutor sorts a list of (name, price) tuples in python/py050/demo/sort_demo.py by name, by price and by price descending with lambda, then counts words in a list with counts[word] = counts.get(word, 0) + 1.

    Hands-on

    Write python/py050/s5/count.py: words = ["pen", "ink", "pen", "pad", "pen", "ink"]; print the sorted unique words on one line joined by spaces, then one "word count" line per word in alphabetical order.

    Explain

    What does key= do in sorted?

    Check

    count.py prints the unique words and counts — checked (T3): seat_file seat_run

    #s5-4

  5. 5.5Lab: the shopping list9 minchecked · T3

    Predict

    max([("pen", 125), ("desk", 12500)], key=lambda t: t[1]) returns?

    Lesson

    A list of records — here (name, category, cents) tuples — plus sorting by a key, counting into a dictionary and max with a key gives you a real report in twenty lines.

    Demo

    The tutor writes python/py050/demo/shop_demo.py with four records, prints them sorted by name with aligned columns, counts per category, and finds the dearest with max(key=...), narrating the key function each time.

    Hands-on

    Write python/py050/s5/shopping.py with items: ("notebook", "paper", 250), ("pen", "writing", 125), ("desk", "furniture", 12500), ("ink", "writing", 450), ("folder", "paper", 1700). Print the names sorted alphabetically one per line, then "category: count" lines in alphabetical order, then "Most expensive: desk".

    Explain

    Why hold each item as a tuple rather than three parallel lists?

    Check

    shopping.py: sorted names, category counts, dearest item — checked (T3): seat_file seat_run

    #s5-5

Session 6 · Functions and tests

Five steps that turn scripts into pieces you can trust. def to name a piece of work, parameters as its inputs and return as its answer — and the difference between returning a value and printing it. Default arguments and keyword arguments. Scope: why a function's variables vanish when it returns and why that is good. pytest: a test file, a test function, an assert, and the red-green rhythm. Live in the seat: a function that prints instead of returning and the None that leaks out, a budget function tested at its boundary, and pytest run from the terminal with one failure read aloud.

Lab Write budget.py with three pure functions — remaining, percent_used and over_budget — and test_budget.py with at least four tests that pass. The grader adds hidden tests of its own. Session 6 checkpoint.

5 mini steps · ~41 min
  1. 6.1def and return7 minchecked · T3

    Predict

    def double(n): print(n * 2) then x = double(4); print(x). What prints on the second line?

    Lesson

    def names a block of code and its parameters; calling it runs the block with those values; return hands a result back to the caller. A function that only prints gives its caller None — the difference matters the moment you test it.

    Demo

    In python/py050/demo/func_demo.py the tutor writes area(width, height) with return, calls it twice, then rewrites it with print instead and shows x = area(2, 3) holding None.

    Hands-on

    Write python/py050/s6/area.py with a function area(width, height) that returns width * height; call it for 3 x 4 and 2.5 x 2 and print both results, then print area(1, 1) + area(2, 2) — three lines: 12, 5.0, 5.

    Explain

    Why return instead of print inside a function?

    Check

    area.py defines and uses area() — checked (T3): seat_file seat_run

    #s6-1

  2. 6.2Parameters, defaults and pure functions8 minchecked · T3

    Predict

    def tip(bill, rate=15): return bill * rate / 100. What is tip(200)?

    Lesson

    A parameter may carry a default, and callers can name arguments: tip(200, rate=20). A pure function uses only its inputs and returns a result without printing or touching anything outside — the same inputs always give the same answer.

    Demo

    The tutor gives tip() a default rate in python/py050/demo/pure_demo.py, calls it positionally and by keyword, then shows an impure version that reads a global total and changes it, and why its result depends on history.

    Hands-on

    Write python/py050/s6/discount.py: a pure function price_after(cents, percent=10) returning the discounted integer cents using // ; print price_after(2000), price_after(2000, 25) and price_after(cents=999, percent=50) — 1800, 1500, 499.

    Explain

    What makes a function pure?

    Check

    discount.py uses a default and keyword arguments — checked (T3): seat_file seat_run

    #s6-2

  3. 6.3Scope: where a name lives7 minchecked · T3

    Predict

    total = 0; def add(n): total = n; add(5); print(total). What prints?

    Lesson

    Names assigned inside a function are local to it and disappear when it returns; the function can read outer names but assigning makes a new local one. Pass values in and return values out instead of reaching for globals.

    Demo

    In python/py050/demo/scope_demo.py the tutor shows a local shadowing a global, prints the outer value unchanged, then refactors to return the new total and assign it outside.

    Hands-on

    Write python/py050/s6/scope.py: balance = 100; a function deposit(balance, amount) that returns the new balance; call balance = deposit(balance, 50) and print balance; then print the result of deposit(0, 1) — lines 150 and 1. No global statement.

    Explain

    Why avoid the global keyword here?

    Check

    scope.py passes and returns instead of using global — checked (T3): seat_file seat_run

    #s6-3

  4. 6.4Your first pytest test9 minchecked · T3

    Predict

    A test file test_area.py has def test_square(): assert area(2, 2) == 5. What does pytest report?

    Lesson

    A test is a function named test_… in a file named test_….py that calls your code and asserts what must be true. pytest finds and runs them and shows what differed. Write the test first, watch it fail, then make it pass.

    Demo

    The tutor writes python/py050/demo/test_area_demo.py with two tests, runs python3 -m pytest demo -q, breaks area() on purpose, reads the failure report line by line, and fixes it.

    Hands-on

    In python/py050/s6/ create test_discount.py importing price_after from discount; write three tests: 2000 at default gives 1800, 2000 at 25 gives 1500, 999 at 50 gives 499. Run python3 -m pytest s6 -q until all pass.

    Explain

    What does a failing assert show you that a print does not?

    Check

    test_discount.py has three passing tests — checked (T3): seat_file seat_test

    #s6-4

  5. 6.5Lab: the budget functions10 minproven · T4

    Predict

    percent_used(budget=0, spent=0) — what should a careful function return?

    Lesson

    Three pure functions over integer cents, each tested at its boundaries: remaining(budget, spent), percent_used(budget, spent) rounded to a whole number and safe for a zero budget, and over_budget(budget, spent) as a bool. The grader will try values you did not.

    Demo

    The tutor sketches the three signatures in python/py050/demo/budget_demo.py, writes one boundary test for each, runs red, implements until green, then adds the zero-budget case and shows the ZeroDivisionError before guarding it.

    Hands-on

    Write python/py050/s6/budget.py with remaining(budget, spent) -> int, percent_used(budget, spent) -> int (round half up; 0 when budget is 0) and over_budget(budget, spent) -> bool, and s6/test_budget.py with at least four tests including the zero budget and an exactly-spent budget. All green.

    Explain

    Why test the zero-budget case explicitly?

    Check

    budget.py passes your tests and the hidden ones — proven (T4): seat_file seat_test craft

    #s6-5

Session 7 · Files and errors

Five steps on data that outlives the program. Opening a text file with with so it is always closed, reading it line by line and stripping the newline. Writing and appending, and the difference between the two modes. CSV as rows and columns through the csv module, reading with a header. try/except for the failures you expect at a boundary — a missing file, a bad number — handled where you can act and reported plainly. Live in the seat: a file read twice without with and the handle left open, a CSV with a quoted comma, FileNotFoundError caught and turned into one clear line and exit code 2.

Lab Write ledger.py: it reads ledger.csv (date, description, cents), prints each row and the total in dollars, and with an extra command-line argument appends a new row. A missing file prints one sentence and exits with code 2. Session 7 checkpoint.

5 mini steps · ~40 min
  1. 7.1Reading a file with with7 minchecked · T3

    Predict

    A file has two lines. for line in f: print(len(line)) with the first line "abc". What prints first?

    Lesson

    with open(path) as f: gives you a file that closes itself when the block ends, even on an error. Walk it with for line in f; each line keeps its newline until you strip it. Paths are relative to where you run python3.

    Demo

    The tutor creates python/py050/demo/notes.txt and reads it in read_demo.py three ways — f.read(), f.readlines() and the for loop — showing the trailing newlines with repr(), then the same without with and f.closed staying False.

    Hands-on

    Copy python/py050/samples/s7/quotes.txt to s7/; write python/py050/s7/read_quotes.py that prints each line numbered from 1 without blank lines between, then "Lines: 4".

    Explain

    What does with guarantee that a plain open() does not?

    Check

    read_quotes.py numbers four lines and counts them — checked (T3): seat_file seat_run

    #s7-1

  2. 7.2Writing and appending7 minchecked · T3

    Predict

    You open a file with mode "w" twice and write one line each time. How many lines does it have?

    Lesson

    open(path, "w") creates or empties the file and writes from the start; "a" appends to the end. write() adds no newline, so put "\n" in yourself. Writing in a with block flushes and closes the file for you.

    Demo

    In python/py050/demo/write_demo.py the tutor writes two lines with "w", shows the file, runs it again to show it still has two, switches to "a" and shows it grow to four.

    Hands-on

    Write python/py050/s7/log_it.py: open s7/runs.txt in append mode and write one line "run" plus a newline; then read the file back and print the number of lines. Run it three times; the third run prints "Runs: 3".

    Explain

    Why did the count grow with "a" but not with "w"?

    Check

    log_it.py appends and counts — checked (T3): seat_file seat_file seat_run

    #s7-2

  3. 7.3CSV: rows and columns8 minchecked · T3

    Predict

    A CSV row is 2026-01-05,"Coffee, large",450. How many fields does csv.reader give?

    Lesson

    csv.reader turns each line into a list of fields and handles quotes for you; csv.DictReader uses the header row as keys. Open CSV files with newline="" as the documentation asks. Everything read is text until you convert it.

    Demo

    The tutor reads python/py050/demo/spend.csv with csv.reader, skips the header with next(), prints each row as a list, then switches to DictReader and prints row["cents"] converted with int() and summed.

    Hands-on

    Copy python/py050/samples/s7/ledger.csv to s7/; write python/py050/s7/sum_csv.py that reads it with DictReader, prints one "description cents" line per row, then "Total: 7775".

    Explain

    Why not split each line on commas yourself?

    Check

    sum_csv.py reads ledger.csv and totals 7775 — checked (T3): seat_file seat_run

    #s7-3

  4. 7.4try/except: the missing file8 minchecked · T3

    Predict

    open("nope.csv") when the file does not exist raises?

    Lesson

    try runs code that might fail; except NameOfError: runs only for that error. Catch the specific error you expect, say what went wrong in one plain line, and exit with a small non-zero code so a script can tell. Never catch everything silently.

    Demo

    In python/py050/demo/missing_demo.py the tutor opens a missing file, shows the traceback, wraps it in try/except FileNotFoundError, prints "Cannot find spend.csv" and calls sys.exit(2); then shows a bare except hiding a typo.

    Hands-on

    Write python/py050/s7/safe_read.py that takes a path from sys.argv[1], prints its line count as "Lines: N", and on a missing file prints "Cannot find <path>" and exits with code 2. Test with s7/quotes.txt and with nope.txt.

    Explain

    Why catch FileNotFoundError and not Exception?

    Check

    safe_read.py counts lines and handles a missing file with exit 2 — checked (T3): seat_file seat_run seat_run craft

    #s7-4

  5. 7.5Lab: the saved ledger10 minchecked · T3

    Predict

    After appending a row to the CSV in mode "a", must you write the header again?

    Lesson

    A ledger that survives: read the CSV, list rows and total in dollars, and with three extra arguments append a row with csv.writer. A missing file is reported in one sentence with exit code 2; everything else leaves the exit code 0.

    Demo

    The tutor builds python/py050/demo/ledger_demo.py in three passes — list and total, append, missing-file guard — running it after each pass and showing the CSV in the editor growing by one row.

    Hands-on

    Write python/py050/s7/ledger.py. python3 s7/ledger.py s7/ledger.csv prints "date description cents" per row then "Total: 77.75". With three more arguments (date, description, cents) it appends that row with csv.writer and prints "Added". A missing file prints "Cannot find <path>" and exits 2.

    Explain

    Why print the total in dollars but store cents in the file?

    Check

    ledger.py lists, totals, appends and guards — checked (T3): seat_file seat_run seat_run

    #s7-5

Session 8 · Capstone: Tally, the expense tracker

Five steps that build one real command-line tool from a written spec. Reading the spec and sketching the functions before typing. argparse for arguments and help text, and an exit-code contract: 0 all good, 2 cannot read the file, 3 some rows rejected. Loading rows and rejecting the bad ones with a reason, without stopping. Totals and a per-category report aligned to the cent with Decimal. Then the whole thing on inputs you have not seen: the grader runs Tally with its own files and its own tests. Live in the seat: the spec turned into four function signatures, --help generated for free, a bad row counted but not crashing, and the report checked against the published expected output.

Lab Finish tally.py: it reads an expenses CSV, prints the total, a category report and a row summary, exits 0, 2 or 3 as specified, and passes your tests and the hidden ones on files you did not write. Course placement step.

5 mini steps · ~43 min
  1. 8.1Reading the spec7 minchecked · T3

    Predict

    Which function should not print anything: load_rows, totals_by_category or main?

    Lesson

    Tally reads expenses.csv (date, category, description, amount) and prints Total, a By category block sorted by name, and "Rows: N ok, M rejected". Before coding, name the pieces: load_rows(path), totals_by_category(rows), format_report(...) and main(argv).

    Demo

    The tutor reads the spec in python/py050/samples/s8/SPEC.md aloud, writes the four signatures with docstrings and pass bodies in demo/tally_skeleton.py, and marks which ones the tests will call.

    Hands-on

    Create python/py050/s8/tally.py with the four function signatures — load_rows(path), totals_by_category(rows), format_report(total, by_cat, ok, rejected) and main(argv) — each with a one-line docstring and a pass body, plus the if __name__ == "__main__": guard calling main(sys.argv[1:]).

    Explain

    Why write the signatures before any logic?

    Check

    tally.py has the four signatures and the main guard — checked (T3): seat_file

    #s8-1

  2. 8.2argparse and the exit-code contract8 minchecked · T3

    Predict

    A caller runs tally.py with no arguments. What should happen under the contract?

    Lesson

    argparse declares the arguments once and gives you --help, error messages and exit code 2 for free. Your contract: 0 when every row loaded, 2 when the file cannot be read, 3 when some rows were rejected but the report still printed.

    Demo

    In demo/args_demo.py the tutor adds an ArgumentParser with a positional csv path and an optional --category filter, runs --help, runs with a missing path and sys.exit(2), and shows the shell reading the exit code with echo $?.

    Hands-on

    In s8/tally.py make main(argv) parse one positional argument "path" with argparse; on FileNotFoundError print "Cannot read <path>" and return 2; otherwise return 0 for now. Return the code from main and pass it to sys.exit in the guard. Try --help and a missing file.

    Explain

    Why return the exit code from main instead of calling sys.exit inside it?

    Check

    tally.py parses its argument and exits 2 on a missing file — checked (T3): seat_file seat_run seat_run

    #s8-2

  3. 8.3Loading and rejecting rows9 minchecked · T3

    Predict

    A row has amount "abc". What should load_rows do?

    Lesson

    load_rows returns (rows, rejected): each good row a dict with the amount as Decimal; a row with fewer than four fields or an amount Decimal cannot parse adds one to rejected. Catch decimal.InvalidOperation, not Exception.

    Demo

    The tutor implements load_rows in demo/load_demo.py over samples/s8/sample.csv, prints the row count and rejected count (5 and 1), then adds a row with three fields to show the second rejection path.

    Hands-on

    Implement load_rows(path) in s8/tally.py using csv.DictReader and Decimal, returning (rows, rejected). Write s8/test_tally.py with a test that loads samples/s8/sample.csv and asserts 5 rows and 1 rejected, and one that a row amount is Decimal("12.50").

    Explain

    Why return the rejected count instead of printing it inside load_rows?

    Check

    load_rows returns 5 rows and 1 rejection for sample.csv — checked (T3): seat_file seat_test

    #s8-3

  4. 8.4Totals and the category report9 minchecked · T3

    Predict

    Categories food, transport and home. In what order does the report list them?

    Lesson

    totals_by_category folds the rows into a dictionary of Decimal totals; format_report builds the text: "Total: 223.45", "By category:", one " name total" line per category sorted by name with widths <12 and >10.2f, then "Rows: 5 ok, 1 rejected".

    Demo

    The tutor writes totals_by_category and format_report in demo/report_demo.py, prints the report for sample.csv, and compares it line by line with samples/s8/expected_sample.txt in a split editor.

    Hands-on

    Implement totals_by_category(rows) and format_report(total, by_cat, ok, rejected) in s8/tally.py; wire main to print the report and return 3 when rejected > 0 else 0. python3 s8/tally.py samples/s8/sample.csv must print exactly samples/s8/expected_sample.txt and exit 3.

    Explain

    Why keep format_report separate from main?

    Check

    tally.py prints the published report for sample.csv — checked (T3): seat_run

    #s8-4

  5. 8.5Lab: Tally on inputs you did not see10 minproven · T4

    Predict

    A file with a header and no data rows. What are the total and the exit code?

    Lesson

    The capstone check: the grader runs your tally.py on files you did not write — a clean file, an empty one, one with every row bad — and runs hidden tests against load_rows and totals_by_category. Your own tests should already cover those shapes.

    Demo

    The tutor shows the three published cases in samples/s8/ (clean.csv exit 0, empty.csv exit 0 with Total: 0.00, allbad.csv exit 3), runs the reference on each, and explains that two more files stay hidden.

    Hands-on

    Make s8/tally.py pass the three published cases and add tests for an empty file and an all-rejected file to s8/test_tally.py. Then ask the tutor to check: the grader runs the hidden files and hidden tests. Fix what it reports.

    Explain

    Why does passing on your own sample prove less than passing on hidden ones?

    Check

    Tally passes the published and hidden files and tests — proven (T4): seat_run seat_test seat_file craft

    #s8-5

Outcome

By the end of the course the tutor's checks will have verified: a working program for every hands-on step; eight session labs that each grow the last — the greeting, the receipt, the tip calculator, the quiz, the shopping list, the budget functions, the saved ledger; and the Tally capstone, a command-line expense tracker whose totals, category report and exit codes match the published expected values on inputs you did not see.

Certificate

Agile System Software Certificate — Python from zero (PY050) attests that you can write, run, test and debug a small command-line Python program that reads and writes files, with functions, collections and an exit-code contract, verified by a machine from your own seat.

To earn it:

  • Pass the lab check of every session (steps 1.5, 2.5, 3.5, 4.5, 5.5, 6.5 and 7.5) on your own code.
  • Pass the Tally capstone check (8.5) including the hidden input files the grader supplies.
  • Score 80% or better on the 20-question reading quiz.

Gates (step · tier required):

Plus a 20-question reading quiz at 80%, retake after 48 h. Certificates are issued once the seat check runner is live; until then every step records what the engine saw.

Environment

A VS Code seat in your browser with Python 3.13, pytest and basedpyright, ready and offline; nothing to install. Your work lives in python/py050; the tutor's demo files live apart from yours and the tutor never edits your hands-on file while you are working on it.

Open Coding Tutor at codingtutor.agilesystemsoftware.com, pick PY050 from the course button in the chat and press Tutor me. Every lesson, demo and hands-on step is also published below.