0%

From Working Code to Program Design · practice

The Cost of Informal Dictionary Shapes

A accepts almost any shape, so Python lets you create each of these values:

good = {
    "prompt": "Two plus two?",
    "answer": "4",
    "points": 1,
}

missing_answer = {
    "prompt": "Three plus three?",
    "points": 1,
}

wrong_points = {
    "prompt": "Four plus four?",
    "answer": "8",
    "points": -5,
}

The dictionaries are valid Python values. Only the first one is a valid quiz question according to our program’s needs.

That difference is important:

  • Python validates dictionary syntax.

  • Your program must validate the meaning and expected shape.

Create records through one function

A construction can establish a dependable shape:

def make_question(prompt, answer, points):
    return {
        "prompt": prompt,
        "answer": answer,
        "points": points,
    }

The function becomes the normal path for creating questions. It can reject unsuitable values before returning a partially useful record.

Fundamentals I introduced raise as the way a function reports that it cannot produce a normal result. Here, the constructor function uses that same tool to protect the rules for a valid question:

if not prompt.strip():
    raise ValueError("A question needs a prompt")

After .strip(), a blank prompt becomes the empty "". You learned in Fundamentals I that an empty string is falsy, so not makes the rejection true.

This improves the design while keeping familiar dictionaries. Every part of the program can receive question records with the same keys and validated values.

What does make_question improve?

This is a useful design on its own. One function now gives the program a consistent way to create question records.

Task

Complete make_question.

Reject a blank prompt, a blank answer, and points below 1 by raising . Otherwise return a with exactly the prompt, answer, and points values supplied by the caller.