← Teacher guide

A class for questions

An optional lesson. Students rebuild the quiz with a Question class whose objects check answers and give feedback, then add a hint method and points, and rebuild their own quiz with objects.

Apps with Gradio · A quiz app · about 45 minutes · Open the student lesson

Classroom resources

Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.

Students will be able to

Timing

Predict and Run 10 min · Investigate 10 min · Modify 15 min · Make 10 min

Before class

Optional: not needed for completing the course. Students who did Objects with turtles have met classes; for others the lesson introduces them briefly. class Question stores each question's text, choices and answer as attributes (self.answer), set in __init__, and has methods check(guess) and feedback(guess). Python passes the object as the first argument of every method, so the first parameter is self (the bug task leaves it out, and the error says it got 2 arguments instead of 1). mark now asks each question to check itself. The info question introduces the idea behind designing with objects: different kinds of question can each have a check method.

Running the lesson

  1. Predict: draw one Question object as a box with its three values and two methods.
  2. Investigate: ask what self is when questions[2].check(...) runs.
  3. Modify: hint first (a method), then points (an attribute in init).

The hard part (on purpose)

What self is inside a method. Ask which question check was called on, and what self.answer is for it.

When students get stuck

What you will seeA question to askThe fix
TypeError: ... takes 1 positional argument but 2 were given.Is self the first parameter of the method?def check(self, guess):.
AttributeError: 'Question' object has no attribute 'points'.Is points stored in __init__ with self.?self.points = points in __init__, and pass a value when making each Question.
NameError: name 'answer' is not defined, inside a method.Does the method use self.answer, or just answer?Use self.answer: the object's own value.

Answers to the checks

  1. Predict: What does the first print show?
    False. q.check("54") compares "54" with q.answer, which is "56". The second print shows Right: What is 7 x 8?
  2. Whose answer?: Inside check, what is self.answer for the second question in the list?
    "8": self is the question check was called on
    questions[1].check(...) runs check with self being questions[1], so self.answer is that question's own answer.
  3. What mark lost: Compared with last lesson, what got simpler in mark?
    It asks each question to check and give feedback, instead of knowing how itself
    The marking rules live in the class. Changing how feedback looks means changing one method, for every question at once.
  4. Spot the bug: q.check("56") stops with TypeError: Question.check() takes 1 positional argument but 2 were given. Click the line that is wrong.
    line 7
    Python passes the object itself as the first argument of every method, so a method's first parameter is self: def check(self, guess):.
  5. Where this leads: You want a second kind of question where the player types the answer. Where does the checking code go?
    In its own class with its own check method, so mark can still just call check on every question
    When every kind of question has a check method, mark does not need to know the difference. Designing with objects is about this.

Solutions

One possible solution for each task. Other correct programs are fine.

Modify › Hints

import gradio as gr

class Question:
    def __init__(self, text, choices, answer):
        self.text = text
        self.choices = choices
        self.answer = answer

    def check(self, guess):
        return guess == self.answer

    def hint(self):
        return "It starts with " + self.answer[0]

    def feedback(self, guess):
        if self.check(guess):
            return "Right: " + self.text
        return "Not quite: " + self.text + " " + self.hint() + "."


questions = [
    Question("Which planet is closest to the Sun?", ["Venus", "Mercury", "Mars"], "Mercury"),
    Question("How many legs does a spider have?", ["6", "8", "10"], "8"),
    Question("What is 7 x 8?", ["54", "56", "64"], "56"),
]


def mark(a1, a2, a3):
    answers = [a1, a2, a3]
    score = 0
    lines = ""
    for i in range(len(questions)):
        if questions[i].check(answers[i]):
            score = score + 1
        lines = lines + questions[i].feedback(answers[i]) + "\n"
    return str(score) + " out of " + str(len(questions)), lines


inputs = []
for q in questions:
    inputs.append(gr.Radio(q.choices, label=q.text))

gr.Interface(fn=mark, inputs=inputs, outputs=[gr.Textbox(label="Score"), gr.Textbox(label="Feedback")], title="Quick quiz").launch()

Modify › Points

import gradio as gr

class Question:
    def __init__(self, text, choices, answer, points):
        self.text = text
        self.choices = choices
        self.answer = answer
        self.points = points

    def check(self, guess):
        return guess == self.answer

    def feedback(self, guess):
        if self.check(guess):
            return "Right: " + self.text
        return "Not quite: " + self.text + " The answer is " + self.answer + "."


questions = [
    Question("Which planet is closest to the Sun?", ["Venus", "Mercury", "Mars"], "Mercury", 1),
    Question("How many legs does a spider have?", ["6", "8", "10"], "8", 1),
    Question("What is 7 x 8?", ["54", "56", "64"], "56", 2),
]


def mark(a1, a2, a3):
    answers = [a1, a2, a3]
    score = 0
    lines = ""
    for i in range(len(questions)):
        if questions[i].check(answers[i]):
            score = score + questions[i].points
        lines = lines + questions[i].feedback(answers[i]) + "\n"
    total = 0
    for q in questions:
        total = total + q.points
    return str(score) + " out of " + str(total), lines


inputs = []
for q in questions:
    inputs.append(gr.Radio(q.choices, label=q.text))

gr.Interface(fn=mark, inputs=inputs, outputs=[gr.Textbox(label="Score"), gr.Textbox(label="Feedback")], title="Quick quiz").launch()

your quiz with objects

import gradio as gr

class Question:
    def __init__(self, text, choices, answer):
        self.text = text
        self.choices = choices
        self.answer = answer

    def check(self, guess):
        return guess == self.answer

    def feedback(self, guess):
        if self.check(guess):
            return "Right: " + self.text
        return "Not quite: " + self.text + " The answer is " + self.answer + "."


questions = [
    Question("Which instrument has 88 keys?", ["Piano", "Organ", "Accordion"], "Piano"),
    Question("How many strings does a violin have?", ["4", "5", "6"], "4"),
    Question("Which family is the trumpet in?", ["Woodwind", "Brass", "Percussion"], "Brass"),
    Question("What does forte mean?", ["Loud", "Soft", "Fast"], "Loud"),
]


def mark(a1, a2, a3, a4):
    answers = [a1, a2, a3, a4]
    score = 0
    lines = ""
    for i in range(len(questions)):
        if questions[i].check(answers[i]):
            score = score + 1
        lines = lines + questions[i].feedback(answers[i]) + "\n"
    return str(score) + " out of " + str(len(questions)), lines


inputs = []
for q in questions:
    inputs.append(gr.Radio(q.choices, label=q.text))

gr.Interface(fn=mark, inputs=inputs, outputs=[gr.Textbox(label="Score"), gr.Textbox(label="Feedback")], title="Music quiz").launch()

Stretch and support

Stretch: Make a TypedQuestion class with its own check that compares lower-case text, for questions answered in a Textbox.

Support: Give students the hint method written out, and ask them only to use it in feedback.

Exit ticket

What is self inside a method?

The object the method was called on, so self.answer is that object's own answer.

What each step is for

Where it leads

Gradio's components are classes too. Machine learning with Gradio (coming later) and games with microStudio build on objects.