0

This is a simple math game. The function of interest is checkAnswer(). I am trying to update label1 so that the label updates with the new str instead of continuously printing multiple labels.

Error:

AttributeError: 'str' object has no attribute 'set'

from tkinter import *

from random import randint

num1 = 0
num2 = 0
userAnswer = 0
answer = 0
score = 0
labeltext = ""

#PROGRAM FUNCTIONS
def question():
    global num1, num2
    global answer
    num1 = randint(1,10)
    num2 = randint(1,10)
    question = Label(text = "What is " + str(num1)+ " + " + str(num2) + "?").pack()
    answer = num1+num2
    print(answer) #testing purposes

def userAnswer():
    global userAnswer
    userAnswer = IntVar()
    entry = Entry(root, textvariable = userAnswer).pack()
    submit = Button(root, text = "submit", command = checkAnswer).pack()

def checkAnswer():
    global labeltext
    print(userAnswer.get())
    if userAnswer == answer:
        labeltext.set("good job")
        score += 1
    elif userAnswer != answer:
        labeltext.set("oh no")
    labeltext = StringVar()
    label1 = Label(root, textvariable = labeltext).pack()

#INTERFACE CODE
root = Tk()

question()
userAnswer()
root.mainloop()
4
  • 2
    You are defining labeltext to be a string at the start of your code. Instead of labeltext.set("good job") you can simply assign the value: labeltext = "good job". Commented Apr 3, 2017 at 12:01
  • 1
    Or you can make it StringVar at the top aswell which would be much easier when updating text of labels. Commented Apr 3, 2017 at 12:51
  • 1
    BTW, the .pack method returns None so entry = Entry(root, textvariable = userAnswer).pack() sets entry to None, it doesn't save the Entry widget to entry. But I guess it doesn't really matter here, since you aren't actually using entry... Commented Apr 3, 2017 at 13:01
  • 2
    Please do not delete your questions after they've received acceptable answers. Questions and answers posted on SO are supposed to benefit future readers as well as the original poster. Commented Apr 7, 2017 at 11:20

1 Answer 1

6

You are getting that AttributeError because you initially bind a string "" to the global name labeltext, and a Python string doesn't have a .set method. (That wouldn't make sense because Python strings are immutable). You eventually bind a Tkinter StringVar to labeltext, and StringVars do have a .set method, but your code does that after it's already tried to call .set on the plain Python string.

A similar problem will occur with the IntVar you named userAnswer. That has an additional problem: its name clashes with one of your functions. You can't do that!

Here's a repaired version of your code, with a few other minor changes. There's no need to use the global directive on those StringVars or the IntVar since you are simply calling methods of those objects. You only need global if you need to perform an assignment on a global object, merely accessing the existing value of a global or calling one of its methods doesn't need the global directive.

from tkinter import *
from random import randint

#PROGRAM FUNCTIONS
def question():
    global true_answer
    num1 = randint(1,10)
    num2 = randint(1,10)
    Label(text="What is " + str(num1)+ " + " + str(num2) + "?").pack()
    true_answer = num1 + num2
    print(true_answer) #testing purposes

def answer():
    Entry(root, textvariable=userAnswer).pack()
    Button(root, text="submit", command=checkAnswer).pack()

def checkAnswer():
    global score
    print(userAnswer.get()) #testing purposes
    if userAnswer.get() == true_answer:
        labeltext.set("good job")
        score += 1
    else:
        labeltext.set("oh no")
    label1 = Label(root, textvariable=labeltext).pack()

#INTERFACE CODE
root = Tk()

true_answer = 0
score = 0

userAnswer = IntVar()
labeltext = StringVar()

question()
answer()
root.mainloop()

However, that code still has several problems. It can only ask a single question. And each time you hit the "submit" button it adds a new Label widget, which I don't think you really want.

It's not a good idea to use global variables. They break modularity, which makes the code harder to understand, and harder to modify and re-use.

Here's an enhanced version of your program which puts everything into a class, so we can use instance attributes instead of globals.

This version asks multiple questions. It doesn't have a "submit" button, instead the question is automatically submitted when the user hits the Enter / Return key, either on the main keyboard or the numeric keypad.

import tkinter as tk
from random import randint

class Quiz(object):
    def __init__(self):
        root = tk.Tk()

        # The question
        self.question_var = tk.StringVar()
        tk.Label(root, textvariable=self.question_var).pack()

        # The answer
        self.user_answer_var = tk.StringVar()
        entry = tk.Entry(root, textvariable=self.user_answer_var)
        entry.pack()
        # Check the answer when the user hits the Enter key,
        # either on the main keyboard or the numeric KeyPad
        entry.bind("<Return>", self.check_answer)
        entry.bind("<KP_Enter>", self.check_answer)
        self.true_answer = None

        # The response
        self.response_var = tk.StringVar()
        self.score = 0
        tk.Label(root, textvariable=self.response_var).pack()

        # Ask the first question
        self.ask_question()
        root.mainloop()

    def ask_question(self):
        num1 = randint(1, 10)
        num2 = randint(1, 10)
        self.question_var.set("What is {} + {}?".format(num1, num2))
        self.true_answer = num1 + num2
        #print(self.true_answer) #testing purposes

    def check_answer(self, event):
        user_answer = self.user_answer_var.get()
        #print(user_answer) #testing purposes

        if int(user_answer) == self.true_answer:
            text = "Good job"
            self.score += 1
        else:
            text = "Oh no"
        self.response_var.set('{} Score={}'.format(text, self.score))

        # Clear the old answer and ask the next question
        self.user_answer_var.set('')
        self.ask_question()

Quiz()

Please note the import tkinter as tk statement. It's much better to use this form than from tkinter import * since that "star" import dumps 130 names into your namespace, which is messy, and can lead to name collisions, especially if you do star imports with other modules. The import tkinter as tk form requires you to do a little more typing, but it also makes the code much easier to read, since it's obvious which names are coming from Tkinter.

I've also changed the names of the variables and the class methods (functions) so they conform to the Python PEP-0008 style guide.

There are various further enhancements that could be made. In particular, this code doesn't gracefully handle user input that isn't a valid integer.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much sir! I really appreciated your help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.