{"count":64,"by_grader":{"rubric":34,"token_roles":5,"self_assessed":4,"code_property":21},"gaps":[{"item":"1.7","stage":1,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Any sentence equivalent to: *\"Python works out the expression on the\nright, producing one object, and then makes the name on the left point at that\nobject.\"*"},{"item":"1.12","stage":1,"form":"label_the_code","grader":"token_roles","gap":"needs token/role pairs in authoring/token_roles.yaml","model_answer":"`\"hello\"` is a text literal, which produces a text object. `len` is a\nname already pointing at a built-in function object. The parentheses mean *use\nthat function now, on what is inside*. `len(\"hello\")` produces the number object\n`5`. `size` is then pointed at that `5`. Order: the literal is made first, the\ncall runs second, the name is pointed last."},{"item":"C1.1","stage":1,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"`x = x` works out the right side (whatever `x` already points at) and\n\npoints `x` at it. Net effect: nothing. It is not an error, *provided* `x` already\nexists; if it does not, Python cannot work out the right side and reports a\n`NameError`."},{"item":"C1.4","stage":1,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"The lookup happens first. Python must know what `total` points at in\n\norder to compute `total * 2`; only after the result exists does the name move."},{"item":"2.1","stage":2,"form":"label_the_code","grader":"token_roles","gap":"needs token/role pairs in authoring/token_roles.yaml","model_answer":"| Line | Event |\n|---|---|\n| `scores = [10, 20]` | moves a name (a new list) |\n| `scores.append(30)` | changes that list object |\n| `scores = [0]` | moves the name (a second, new list) |\n| `total = 0` | moves a name (a number) |\n\nNote that after line 3 the list `[10, 20, 30]` has no name left and is gone."},{"item":"2.4","stage":2,"form":"compare_near_matches","grader":"self_assessed","gap":"fewer than two labelled snippets to compare","model_answer":"In 2.2, line 3 **changed the one list that both names point at**. In\n2.3, line 3 **built a second list and pointed `a` at it, leaving `b` on the\nfirst**."},{"item":"2.13","stage":2,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"Splitting by rebinding (safe):\n\n```python\na = [1, 2]\nb = a\na = a + [3]\nprint(a, b)           # [1, 2, 3] [1, 2]\n```\n\nReaching a similar-looking end by changing (dangerous):\n\n```python\na = [1, 2]\nb = a\nb.append(3)\nprint(a, b)           # [1, 2, 3] [1, 2, 3]  \u2014 note: NOT the same output\n```\n\nThe honest answer to the last part: the second program cannot actually reproduce\nthe first program's output, "},{"item":"2.16","stage":2,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"> \"If the thing immediately to the left of the `=` is a bare name, then the line\n> **points that name at whatever the right side produced, and changes no object**.\"\n>\n> \"If a line has no `=` at all but ends in `.something(...)`, then the line\n> **may change the object it was called on, and moves no name**.\""},{"item":"2.17","stage":2,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints `[1, 2, 3, 4]` twice.\n\n**Diagnosis.** Two separate mistakes stack here, and you should name both: line 2\ndid not copy (so `base` and `bigger` are one list), and line 3 uses `+=`, which\nfor a list changes that shared object rather than making a new one. Either\nmistake alone would have been survivable; `bigger = base` followed by\n`bigger = bigger + [4]` would have printed the intended resu"},{"item":"C2.1","stage":2,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Change an object: `x.append(1)`, `x.sort()`, and `x += [1]` when `x`\n\nis a list. Move a name: `x = x + 1`, `x = []`."},{"item":"C2.3","stage":2,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"`.upper()` does not change the text object (it cannot); it produces a\n\nnew one, so the assignment is the only way to keep the result. `.sort()` does\nchange the list and hands back `None`, so the assignment throws away the sorted\nlist and stores nothing."},{"item":"C2.4","stage":2,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"Yes, if it holds a changeable object:\n\n\n```python\nt = ([1], 2)\nt[0].append(9)        # t is now ([1, 9], 2)\n```\n\nThe tuple's own two slots never changed."},{"item":"C2.6","stage":2,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Because a number object cannot be changed, so `+=` has no option but\n\nto build a new one and rebind; a list can change itself, so `+=` asks it to, and\nno name needs to move."},{"item":"3.1","stage":3,"form":"label_the_code","grader":"token_roles","gap":"needs token/role pairs in authoring/token_roles.yaml","model_answer":"| Line | Event | Name moved? |\n|---|---|---|\n| `row = [10, 20, 30]` | builds a list, points `row` at it | yes, `row` |\n| `first = row[0]` | reads slot 0, points `first` at that object | yes, `first` |\n| `row[2] = 99` | changes the list's slot 2 | no |\n| `row = row[0:2]` | builds a **new** two-item list, points `row` at it | yes, `row` |\n\nAfter line 4 the three-item list `[10, 20, 99]` has no name "},{"item":"3.5","stage":3,"form":"label_the_code","grader":"token_roles","gap":"needs token/role pairs in authoring/token_roles.yaml","model_answer":"Step 1: `record` points at a dictionary. Step 2: `record[\"scores\"]`\nlooks up the key `\"scores\"` and produces the list object `[7, 9, 8]`. Step 3:\n`[1]` is applied to *that list* and produces the number `9`. Step 4: `value` is\npointed at `9`."},{"item":"3.11","stage":3,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints `[[1, 2], 0]`. Line 2 replaced the *entire second row* with\nthe number `0`, because `grid[1]` names the whole row slot.\n\n**Fix.** Reach one level further in:\n\n```python\ngrid[1][0] = 0\nprint(grid)           # [[1, 2], [0, 4]]\n```"},{"item":"3.15","stage":3,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"A correct solution:\n\n```python\nscores = {\"ann\": [7, 9], \"bo\": [8], \"cy\": [6, 6]}\nscores[\"bo\"].append(10)          # changes the inner list only\nscores[\"cy\"] = [1, 2, 3]         # changes the dictionary only\nprint(scores)\n# {'ann': [7, 9], 'bo': [8, 10], 'cy': [1, 2, 3]}\n```\n\n**Classification.** Line 2 changed a list inside the dictionary; the dictionary's\nown pairs are untouched \u2014 `\"bo\"` still poi"},{"item":"3.17","stage":3,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"> \"When the left of the `=` has brackets on it, the line changes **the object\n> those brackets are reaching into**, and the name in front of the brackets\n> **does not move \u2014 it still points at the same object it did before**.\"\n\nReading `a[b][c]`: *\"work out `a[b]` first, producing some object, then apply\n`[c]` to that object\"* \u2014 strictly left to right, one level per bracket pair."},{"item":"C3.1","stage":3,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"The list object that `row` points at is changed, at slot 2. No name is\n\nrebound; `row` points where it always did."},{"item":"C3.3","stage":3,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Because a tuple's contents can never change after it is built, so a\n\ndictionary can always find it again; a list could be changed after being filed\nand the dictionary would no longer recognize it."},{"item":"C3.5","stage":3,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"`menu` must be a dictionary with a `\"drinks\"` key; `menu[\"drinks\"]`\n\nmust produce something indexable by position, i.e. a list or tuple;\n`menu[\"drinks\"][0]` must produce a dictionary with a `\"price\"` key; the whole\nexpression produces that price."},{"item":"C3.6","stage":3,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Absent key: the dictionary gains a pair, and the new key goes **last**\n\nin order. Present key: the number of pairs is unchanged, the key keeps its\noriginal position, and only the value it points at is replaced."},{"item":"4.4","stage":4,"form":"compare_near_matches","grader":"self_assessed","gap":"fewer than two labelled snippets to compare","model_answer":"\"A one-level copy protects **the outer container \u2014 its length, the\norder of its slots, and which objects those slots point at** \u2014 and does not\nprotect **the objects those slots point at, which remain shared**.\""},{"item":"4.14","stage":4,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\nrows = [[0, 0], [0, 0], [0, 0]]\nrows[0][0] = 5\nprint(rows)                              # [[5, 0], [0, 0], [0, 0]]\nprint(rows[0] is rows[1], rows[1] is rows[2])   # False False\n```\n\n**To share on purpose**, bind the inner list to a name and use that name three\ntimes \u2014 `shared = [0, 0]` then `rows = [shared, shared, shared]` \u2014 or write\n`[[0, 0]] * 3`. Sharing is a legitimate thing to want"},{"item":"4.15","stage":4,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints `[[1, 2, 3], [1, 2, 3]]`. Both entries in `history` are the\nsame list \u2014 the one `current` points at \u2014 so the \"past state\" was overwritten by\nthe later change, and appending it a second time just added a second arrow to it.\n\n**Smallest fix.** Store a copy at the moment of recording:\n\n```python\nhistory.append(current[:])\n...\nhistory.append(current[:])\n```"},{"item":"4.17","stage":4,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"> \"`b = a[:]` gives me a new **outer list** whose slots point at **the very same\n> objects `a`'s slots point at**.\"\n>\n> \"`[x] * n` repeats **the arrow to one object**, not **the object itself**.\"\n>\n> \"I should reach for a deep copy only when **I need an independent snapshot of a\n> nested structure that will keep changing** \u2014 not as a routine fix for sharing I\n> have not understood.\""},{"item":"C4.1","stage":4,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Only `b[0].append(3)` affects `a`. `b.append([3])` and `b[0] = [3]`\n\nchange `b`'s own outer list; `b = [3]` does not change any object at all, it just\nmoves the name `b`."},{"item":"C4.3","stage":4,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Because `0` cannot be changed, so having the same `0` in five slots is\n\nindistinguishable from having five, whereas the same empty list in five slots can\nbe changed once and appear to change five times."},{"item":"C4.4","stage":4,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Any two of: it duplicates objects that were meant to be shared, so the\n\nprogram silently stops working as designed; it costs time and memory proportional\nto the whole structure; it hides the real question of which level needed\nprotecting, so the next bug of this kind is just as mysterious; and it can fail or\nbehave oddly on objects that are not meant to be duplicated."},{"item":"C4.6","stage":4,"form":null,"grader":"self_assessed","gap":"checkpoint item carries no form label","model_answer":"(1) *Did I copy at all?* No \u2014 `x = y[\"k\"]` is a lookup, which produces\n\nthe existing object, not a copy. (2) *Which level did I copy?* None. (3) *Is the\nobject I changed above or below the copy level?* There is no copy level; `x` and\n`y[\"k\"]` are one list, so the change is shared. Output would be `{'k': [1, 2]}`."},{"item":"5.12","stage":5,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints `[4]`. Line 5 **rebinds** `evens` to a brand-new one-item\nlist on every matching pass, so only the last match survives.\n\n**Smallest fix.**\n\n```python\nevens.append(n)\n```"},{"item":"5.14","stage":5,"form":"find_and_fix","grader":"self_assessed","gap":"no runnable snippet to define equivalence","model_answer":"Build a new list and leave the original alone during the walk:\n\n```python\nnums = [1, 2, 3, 4]\nodds = []\nfor n in nums:\n    if n % 2 == 1:\n        odds.append(n)\nprint(odds)               # [1, 3]\n```\n\nIf the original name must end up holding only the odds, rebind it *after* the\nloop: `nums = odds`. If some other name must also see the change, replace the\ncontents in one step after the loop: `nums["},{"item":"5.17","stage":5,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\nsquares = []\nfor n in range(1, 6):\n    squares.append(n * n)\nprint(squares)        # [1, 4, 9, 16, 25]\nprint(len(squares))   # 5\n```"},{"item":"5.19","stage":5,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"1. By indentation: the body is exactly the lines indented under the `for` line's\n   colon, up to the first line that returns to the previous level.\n2. The `for` line runs one more time than the body \u2014 the extra visit is the one\n   that finds nothing left and ends the loop.\n3. Nothing happens to it: it stays bound to the last value it was given, and can\n   be read after the loop.\n4. Never by assign"},{"item":"C5.3","stage":5,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"`x = 0` rebinds the loop name to the number 0 and never touches the\n\nlist's slots, so the list is unchanged; the smallest working alternative is\n`for i in range(len(xs)): xs[i] = 0`."},{"item":"C5.6","stage":5,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Because a range produces its numbers only as they are asked for, so it\n\nhas no list to show; `print(list(range(5)))` builds and shows one."},{"item":"6.4","stage":6,"form":"label_the_code","grader":"token_roles","gap":"needs token/role pairs in authoring/token_roles.yaml","model_answer":"`for` opens a loop and a **block**, ended by the colon.\n`scores.items()` produces an **iterable** of two-item tuples, one per key\u2013value\npair. `k, v` are two **loop variables**: each pass takes the tuple handed over and\n**unpacks** it, **binding** `k` to the first item and `v` to the second. The\nindented block then runs once per pass."},{"item":"6.9","stage":6,"form":"predict_the_result","grader":"rubric","gap":"output is not guaranteed (set order / id / repr); needs an authored rubric of acceptable answers","model_answer":"```text\n3\n[1, 2, 3]\n3\n```\n\n**What you cannot safely predict:** the *order* in which the set would hand out\nits items if you looped over it directly. The `sorted(...)` line is predictable\nprecisely because it does not depend on set order. Also note `nums.add(1)`\nchanged nothing \u2014 1 was already there, and a set holds at most one of each item,\nso `len` stays 3."},{"item":"6.10","stage":6,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"The loop prints the three words in an order determined by how the set\nhappens to file them. For strings, that filing depends on a value Python\nrandomizes at start-up, so **the order can differ between runs of the same\nprogram on the same machine**. Code that appeared to work yesterday can print\ndifferently today.\n\n**Fix.**\n\n```python\nfor w in sorted(words):\n    print(w)              # apple, fig, "},{"item":"6.17","stage":6,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It raises `RuntimeError: dictionary changed size during iteration`.\n\n**Why.** Deleting a key while the loop is walking the dictionary invalidates the\nwalk. Unlike a list \u2014 which shrinks silently and skips items (5.13) \u2014 a dictionary\ndetects the change and stops you.\n\n**Fix.** Walk a snapshot of the keys:\n\n```python\nfor k in list(counts):\n    if counts[k] == 0:\n        del counts[k]\nprint(counts)  "},{"item":"6.18","stage":6,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\ntext = \"hello world\"\ncounts = {}\nfor c in text:\n    if c == \" \":\n        continue\n    counts[c] = counts.get(c, 0) + 1\nfor c in sorted(counts):\n    print(c, counts[c])\n```\n\nOutput:\n\n```text\nd 1\ne 1\nh 1\nl 3\no 2\nr 1\nw 1\n```"},{"item":"6.20","stage":6,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"1. The keys. *Formally:* iterating a dictionary yields its keys; `.values()` and\n   `.items()` yield the other views.\n2. Square brackets build the whole list now; round brackets build a recipe that\n   produces values on demand. *Formally:* a list comprehension versus a generator\n   expression \u2014 eager versus lazy, multi-pass versus one-pass.\n3. Because a range recomputes its numbers from the start "},{"item":"C6.2","stage":6,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Walkable more than once: `[1, 2, 3]`, `range(3)`, `\"abc\"`, `{1, 2}`,\n\nand `{\"a\": 1}.items()` (a view, which re-walks the live dictionary). Only\n`(n for n in range(3))` is one-pass."},{"item":"C6.4","stage":6,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Unpacking `i, x` requires each item handed over to be a container of\n\nexactly two things; the items here are plain numbers, so Python reports\n`TypeError: cannot unpack non-iterable int object`. The fix is `for i, x in\nenumerate(xs):`, or `for x in xs:` if the position is not needed."},{"item":"C6.6","stage":6,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"An **iterable** is anything a `for` loop can walk. A **loop variable**\n\nis the name the `for` line rebinds on each pass. **Hashable** means the object can\nbe filed by what it looks like and will not change afterward, so it may be used as\na dictionary key or a set item."},{"item":"7.6","stage":7,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints:\n\n```text\nfound\nsearch over\n```\n\nwhich *looks* correct \u2014 and that is the problem. The `break` left only the inner\nloop; the outer loop then continued and scanned `[3, 4]` in full. With a target in\nthe first of a thousand rows, the program still reads all thousand. Change\n`target` to 3 and you would see `found` printed while the first row was scanned\npointlessly first.\n\n**Fix with a flag."},{"item":"7.12","stage":7,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\ntable = []\nfor r in range(3):\n    row = []\n    for c in range(3):\n        row.append((r + 1) * (c + 1))\n    table.append(row)\n\nfor row in table:\n    print(row)\n```\n\nOutput:\n\n```text\n[1, 2, 3]\n[2, 4, 6]\n[3, 6, 9]\n```\n\nOr as a comprehension:\n\n```python\ntable = [[(r + 1) * (c + 1) for c in range(3)] for r in range(3)]\n```"},{"item":"7.19","stage":7,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"1. Multiply the pass counts of every loop it is inside \u2014 outer \u00d7 inner (\u00d7 any\n   further levels).\n2. Only the loop it is directly inside; control resumes at the next line of the\n   enclosing body.\n3. From the start \u2014 the inner loop's iterable is walked afresh on every outer\n   pass.\n4. By its indentation: it belongs to the nearest colon-block above it that is\n   indented less than it is."},{"item":"C7.3","stage":7,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"The `break` leaves only the inner loop, so the outer loop keeps going.\n\nRepairs: set a flag and `break` again in the outer body; or move the search into a\nfunction and `return` (Stage 8)."},{"item":"8.13","stage":8,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"`UnboundLocalError: cannot access local variable 'count' where it is\nnot associated with a value`.\n\n**Why.** Python decides, when it builds the function object, which names in the\nbody are local \u2014 and the rule is: **a name assigned anywhere in the body is local\nfor the whole body**, including lines before the assignment. `count = count + 1`\nassigns to `count`, so `count` is local throughout; the r"},{"item":"8.14","stage":8,"form":"predict_the_result","grader":"rubric","gap":"output is not guaranteed (set order / id / repr); needs an authored rubric of acceptable answers","model_answer":"```text\n(3, 1)\n3 1\ntuple\n```"},{"item":"8.17","stage":8,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\ndef collect(item, acc=None):\n    if acc is None:\n        acc = []\n    acc.append(item)\n    return acc\n\nprint(collect(\"a\"))     # ['a']\nprint(collect(\"b\"))     # ['b']\n```\n\n**The line that does the work: `acc = []`,** because it is *inside the body* and\ntherefore runs on every call that reaches it, producing a fresh list each time.\nThe `acc=None` in the header is only a signal; `None` is "},{"item":"8.19","stage":8,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\ndef tally(words):\n    counts = {}\n    for w in words:\n        counts[w] = counts.get(w, 0) + 1\n    return counts\n\ndata = [\"a\", \"b\", \"a\"]\nfirst = tally(data)\nsecond = tally(data)\nprint(first)                    # {'a': 2, 'b': 1}\nprint(data)                     # ['a', 'b', 'a']  \u2014 unchanged\nprint(first is second)          # False \u2014 a fresh dictionary per call\nfirst[\"z\"] = 99\nprint(second"},{"item":"8.21","stage":8,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"1. It builds a function object holding the body and binds the function's name to\n   it \u2014 it does not run the body.\n2. The object produced by the argument expression, bound to the parameter name in\n   a fresh set of local names for that call.\n3. Only when the body **changes an object** the caller can also reach; rebinding a\n   parameter is invisible outside.\n4. `return` hands back an object and end"},{"item":"C8.2","stage":8,"form":"predict_the_result","grader":"rubric","gap":"output is not guaranteed (set order / id / repr); needs an authored rubric of acceptable answers","model_answer":"It prints `None` and then something like\n\n`<function f at 0x7f...>`. The second line is surprising because naming a\nfunction does not call it \u2014 you get the object itself, address and all."},{"item":"C8.3","stage":8,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Python does not copy arguments; the parameter is a new *name* bound to\n\nthe *same object* the caller passed. A function can therefore change the caller's\nlist freely \u2014 `def f(xs): xs.clear()` \u2014 while rebinding the parameter cannot affect\nthe caller at all."},{"item":"C8.6","stage":8,"form":"state_the_rule","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Because `items = []` binds the local parameter name to a brand-new\n\nlist; it does not touch the object that name previously pointed at, and the\ncaller's name still points at that original object. To clear the caller's list the\nbody would have to change the object itself \u2014 `items.clear()` or `items[:] = []`."},{"item":"9.1","stage":9,"form":"predict_then_diagnose","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"`['ann', 'bo'] ['ann', 'bo'] True`\n\n**Diagnosis.** `register` changes the list it is given and then returns that same\nobject. So `team_b` is not a second team \u2014 it is a second name for `team_a`. The\nsecond call appends to the one list through the other name. The `True` is the\nproof.\n\nThe defect is a design one, on the `return roster` line combined with the\n`.append`: the function both changes its "},{"item":"9.6","stage":9,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints `[[1], [], [2]]` \u2014 one empty row survives.\n\n**Trace.**\n\n| loop is at position | `grid` at that moment | `row` | action |\n|---|---|---|---|\n| 0 | `[[1], [], [], [2]]` | `[1]` | not empty, nothing |\n| 1 | `[[1], [], [], [2]]` | `[]` | remove \u2192 `[[1], [], [2]]` |\n| 2 | `[[1], [], [2]]` | `[2]` | not empty, nothing |\n| 3 | `[[1], [], [2]]` | \u2014 | past the end; loop ends |\n\nThe removal shifted"},{"item":"9.9","stage":9,"form":"find_and_fix","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"It prints `[['bo', 'ann'], ['bo', 'ann']]`.\n\n**Diagnosis.** Both entries in `rounds` are the same list \u2014 the one `standings`\npoints at. `.reverse()` changed that list in place, so the \"round 1 record\" was\nrewritten retroactively, and the second append merely added a second arrow to it.\n\n**Fix.** Record a copy at each round:\n\n```python\nrounds.append(standings[:])\nstandings.reverse()\nrounds.append(s"},{"item":"9.10","stage":9,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\ndef transpose(grid):\n    result = []\n    for c in range(len(grid[0])):\n        new_row = []\n        for r in range(len(grid)):\n            new_row.append(grid[r][c])\n        result.append(new_row)\n    return result\n\ng = [[1, 2, 3], [4, 5, 6]]\nt = transpose(g)\nprint(t)                              # [[1, 4], [2, 5], [3, 6]]\nprint(g)                              # [[1, 2, 3], [4, 5, 6]] \u2014 "},{"item":"9.C5","stage":9,"form":"predict_then_diagnose","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"**Defect 1 \u2014 line 2, `[[0] * cols] * rows`.**\n*Event:* `[0] * cols` is evaluated **once**, producing one list; the outer `* rows`\nthen builds an outer list whose slots all point at that one object. Writing any\ncell writes every row's cell.\n*Misconception:* that `*` on a list of lists produces independent inner lists.\n*Type:* object-model.\n\n**Defect 2 \u2014 line 4, `log=[]`.**\n*Event:* the default list"},{"item":"9.C6","stage":9,"form":"predict_then_diagnose","grader":"rubric","gap":"needs required/forbidden phrase sets in authoring/rubrics.yaml","model_answer":"Defect 2, the mutable default `log=[]`.\n\nIt is invisible because the program creates only one board and reads the log only\nonce. Every entry that lands in the shared list belongs there, so the output looks\nright.\n\n**Two lines that expose it:**\n\n```python\nboard2 = new_board(3, 3)\nprint(record(board2, 0, 0, 1))\n```\n\nThis prints:\n\n```text\n[(0, 0, 5), (1, 1, 7), (2, 2, 12), (0, 0, 1)]\n```\n\nA brand-new"},{"item":"9.C8","stage":9,"form":"write_a_small_program","grader":"code_property","gap":"needs assertions in authoring/code_properties.yaml: what must be true of the learner's program (bindings, aliasing, output). The answer key's fix could not be spliced into the buggy program and executed.","model_answer":"```python\nimport copy\n\ndef snapshot(board):\n    return copy.deepcopy(board)\n\nboard = new_board(3, 3)\nhistory = record(board, 0, 0, 5)\nbefore = snapshot(board)\nrecord(board, 1, 1, 7, history)\nprint(before)        # [[5, 0, 0], [0, 0, 0], [0, 0, 0]]\nprint(board)         # [[5, 0, 0], [0, 7, 0], [0, 0, 0]]\n```\n\n**Justification.** A board is a list of lists, and scoring writes into the *inner*\nlists \u2014"}]}