1

I'm creating a program in Python 3.x where a quiz containing randomly generated simple arithmetic questions (e.g. 3 x 8). The quiz will be played by 3 different classes of school students (not real ones!), and the latest 3 scores of each student should be stored. The scores for each class should be kept separately (in 3 text files).

After the quiz is played by a student, the student's score for that try should be added to their scores.

I created the following code, where filename is the name of the text file which stores the student's class' scores, score is the student's score that they just acheived, fullName is the student's full name (which they inputted at the start), and scores is a dictionary which stores the user's class' scores:

with open(filename, "a+") as file:
    scores = ast.literal_eval(file.read())
#loads student's class' scores into dict

if fullName not in scores:
    scores[fullName] = collections.deque(maxlen=3)
#adds key for student if they haven't played before

scores[fullName].append(score)
#adds student's new score to their scores

with open(filename, "w") as file:
    file.write(str(scores))
#writes updated class scores to student's class' file

But when I run it, I receive an error:

Traceback (most recent call last):
  File "E:\My Documents\Quiz.py", line 175, in <module>
    menu()
  File "E:\My Documents\Quiz.py", line 142, in menu
    scores = ast.literal_eval(file.read())
  File "C:\Python34\lib\ast.py", line 46, in literal_eval
    node_or_string = parse(node_or_string, mode='eval')
  File "C:\Python34\lib\ast.py", line 35, in parse
    return compile(source, filename, mode, PyCF_ONLY_AST)
  File "<unknown>", line 0

    ^
SyntaxError: unexpected EOF while parsing

I thought that this was because the text files were empty, so when the program tried to read from them, it produced an error. So, I changed it to this:

with open(filename, "a+") as file:
    try:
        scores = ast.literal_eval(file.read())
    except SyntaxError:
        scores = {}
#loads student's class' scores into dict, but if
#text file is empty, it creates empty dict by itself.

if fullName not in scores:
    scores[fullName] = collections.deque(maxlen=3)
#adds key for student if they haven't played before

scores[fullName].append(score)
#adds student's new score to their scores

with open(filename, "w") as file:
    file.write(str(scores))
#writes updated class scores to student's class' file

But when I run the program multiple times, either with different names or with the same name, only the score from the newest try appears. I tried having data already in a text file and then running the program without the try...except statement, but the same SyntaxError still occurred. Why is this happening? Please note that I am a beginner, though, so I may not understand a lot of things.

BobZeBuilder
  • 89
  • 1
  • 4
  • 11
  • Why are you using literal_eval?Also why `"a+"` to open the file? – Padraic Cunningham Apr 17 '16 at 17:52
  • @PadraicCunningham Because I read that it's a way to read a file so that it can be used in a dictionary. I used `"a+"` because I don't want the contents of the file to be truncated (deleted), I want to be able to read from the file, and I want the file to be created if it doesn't already exist. – BobZeBuilder Apr 17 '16 at 17:54
  • You should pickle the dict if you want to store a deque, same sort of logic http://stackoverflow.com/questions/36365295/trying-to-input-a-integer-into-a-file-and-retrieving-it-as-one-python-3x/36365934#36365934, you don't want to create the file if it does not exist, you only want to try to read from it if it does exists, you are overwriting each time you dump to file with `w` so opening for reading is all you need to do. – Padraic Cunningham Apr 17 '16 at 17:55
  • Not sure why you are using literal_eval or 'a+' for that matter? What is a sample output, (student_name:score1, score2, score3... or student1_name:score\nstudent2_name:score\nstudent3_name:score) – TheLazyScripter Apr 17 '16 at 18:18
  • @TheLazyScripter At the moment, one of the class' text files contains: `{'Bobby Berry': deque([1], maxlen=3)}` – BobZeBuilder Apr 17 '16 at 18:20
  • @BobZeBuilder Not sure why you are doing it like that? I would suggest a Score class. Class contains a container 'high_scores'. Create a loader for loading data specifically to the container and a saver to save data to the txt file in a specific order that the loader can then load it. I would also usually recommend using .xml rather than .txt, but it might be too complex for the scope of your problem! – TheLazyScripter Apr 17 '16 at 18:23
  • @PadraicCunningham I'm not sure I understand that pickle thing. I **do** want to create the file if it doesn't exist, though. And yes, `w` does make it overwrite the text file, but it should be overwriting it with what is in the `scores` dict (which should be the scores previously stored in the text file as well as the new score). – BobZeBuilder Apr 17 '16 at 18:24
  • Why would you be creating the file when you are going to always be overwriting it? You should be catching an IOError trying to opening the fiel for reading as per the logic in the link I provided for pickle – Padraic Cunningham Apr 17 '16 at 18:25
  • @PadraicCunningham I believe he want's it to be viewable outside of python. – TheLazyScripter Apr 17 '16 at 18:26
  • @TheLazyScripter Sorry but I don't understand, I'm new to Python and all programming in general. :( – BobZeBuilder Apr 17 '16 at 18:28
  • @TheLazyScripter The scores are going to be able to be viewed by teachers using my program, so it'll be viewed in Python. – BobZeBuilder Apr 17 '16 at 18:28
  • @BobZeBuilder Okay that makes sense! – TheLazyScripter Apr 17 '16 at 18:29
  • Can you choose one of the following preferred outputs? 1. (Bobby:400, 354, 12\nJane:223, 95, 1\n Tom:200,30,12) or 2. Bobby:400\nJane:223\nTom:200 – TheLazyScripter Apr 17 '16 at 18:29
  • @TheLazyScripter, the OP is using a deque which cannot be literal_eval'd. they have to pickle if they want to store the deque or else convert it to a list – Padraic Cunningham Apr 17 '16 at 18:29
  • @PadraicCunningham I don't understand :( I thought that `a+` only creates a file if it doesn't already exist? – BobZeBuilder Apr 17 '16 at 18:30
  • @BobZeBuilder it opens a file for reading and writing but anything you write is appended. If you want your code to work just use a list in place of the deque. A deque cannot be used with literal_eval, you can do it just before you write to the file if you want to use a deque in your code but it is probably simpler just to use a list and remove/replace the oldest score – Padraic Cunningham Apr 17 '16 at 18:31
  • @PadraicCunningham But if I use a list, how will I make it so that the latest 3 scores are stored? – BobZeBuilder Apr 17 '16 at 18:33
  • Think about how you might, if your user has 3 scores already and you want to add a new score, which one do you think you need to replace?And ofc if the len(lst) is < 3 you don't do anything but add. – Padraic Cunningham Apr 17 '16 at 18:34
  • @PadraicCunningham The first item in the list? So I'd use if statements to see if the length of the list is < 3, and if it isn't then I'd replace the first item, right? – BobZeBuilder Apr 17 '16 at 18:35
  • scores.append(latest_score) adds new score to end of list, therefore the first is the oldest. del scores[0] removes the first or oldest score from the list! – TheLazyScripter Apr 17 '16 at 18:36
  • @BobZeBuilder, yes, exactly, `new = lst[1:] new.append(new_score)`. – Padraic Cunningham Apr 17 '16 at 18:36
  • @PadraicCunningham Is that supposed to be on 2 lines or is it on 1? And what does the `[1:]` mean? Also, in my code, would I change `scores[fullName] = collections.deque(maxlen=3)` to `scores[fullName] = []`? – BobZeBuilder Apr 17 '16 at 18:41
  • Two lines, it slices from the second score then you append the new. Yes you would use the list, just make sure you only slice the list when the len is 3 else just append – Padraic Cunningham Apr 17 '16 at 18:51
  • Okay, thanks! :) So what about the permissions? What should I change them to? – BobZeBuilder Apr 17 '16 at 19:32
  • Also, how would I delete the last score in `scores[fullName]`, because it's not a normal list, it's a key in a dictionary? Do I do `scores[fullName] = scores[fullName][1:]`? – BobZeBuilder Apr 17 '16 at 19:39
  • @PadraicCunningham Hello? – BobZeBuilder Apr 18 '16 at 19:47
  • @BobZeBuilder, hello, unless you use the `@` I won't get notified, I will write an example for you later as I think it will be easier show you than to try to explain it. – Padraic Cunningham Apr 18 '16 at 19:48
  • @PadraicCunningham Ah, okay. Sorry, I was unaware of that. Okay, thanks. – BobZeBuilder Apr 18 '16 at 20:02
  • Are you saving all the players in one dict yes? – Padraic Cunningham Apr 18 '16 at 20:27
  • @PadraicCunningham Yes, well, one of the classes (contents of one text file), that is. – BobZeBuilder Apr 18 '16 at 20:29
  • Is it one question per run? – Padraic Cunningham Apr 18 '16 at 20:30
  • Do you mean each run of the program (playing of the quiz)? If so, then when a student plays the quiz, they answer 10 questions and their score out of 10 is what I'm trying to store, along with their own and everyone else in their class' scores. – BobZeBuilder Apr 18 '16 at 20:33
  • Ok, I will add an answer. – Padraic Cunningham Apr 18 '16 at 20:38

1 Answers1

1

You could use a deque and pickle but to keep it simple we will use json to dump a dict using lists to hold the values:

# Unless first run for class we should be able to load the dict.

    try:
        with open(filename, "r") as f:
            scores = json.load(f)
    except FileNotFoundError:
        scores = {}

# game logic where you get score...

# try get users list of scores or create new list if it is their first go.
student_list = scores.get(fullName, [])

# if the user has three score remove the oldest and add the new.
# student_list[1:]  takes all but the first/oldest score.
if len(student_list) == 3:
     scores[fullName] = student_list[1:]  + [score]
else:
     # else < 3 so just append the new score.
     student_list.append(score)
     scores[fullName] = student_list


# dump dict to file.
with open(filename, "w") as f:
    json.dump(scores, f)
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • Thank you very much! I understand it! :) What if it's the first run for a class? Also, I assume that this can't be done using a .txt file, then? And can a json be opened and read properly in Notepad? – BobZeBuilder Apr 18 '16 at 21:17
  • @BobZeBuilder, the file can be anything, json stores the data in human readable format, it will still look just like a dict in the file. If it is the first run then scores will be set to an empty dict and we will just dump the dict creating the file if it does not exist or just overwriting the content if it does – Padraic Cunningham Apr 18 '16 at 21:22
  • Ah, okay, thanks. One more thing, in `scores[fullName] = student_list[1:] + [score]`, why is the `score` variable in square brackets? Shouldn't it just be `score`? – BobZeBuilder Apr 18 '16 at 21:30
  • I just tested the program with this but I got this error? `with open(filename, "r") as f: FileNotFoundError: [Errno 2] No such file or directory: 'Class_C.txt'` – BobZeBuilder Apr 18 '16 at 21:41
  • @BobZeBuilder, use the edit. The score is inside a list because we are concatenating two lists – Padraic Cunningham Apr 18 '16 at 21:43
  • Ah ok. I tried it again, but it's still giving me the same error. I think it's because it's trying to open the file before it even gets to the try...except statement. So should I put the `with open(filename, "r") as f:` in a try...except statement? – BobZeBuilder Apr 18 '16 at 21:52
  • Yep, I just copy/pasted your code I thought the with was inside, it is fixed now ;) – Padraic Cunningham Apr 18 '16 at 21:54
  • 1
    Ok. It works now. Thank you very much for your help, I greatly appreciate it! :) – BobZeBuilder Apr 18 '16 at 22:03
  • Oh yeah, another thing: is the file supposed to be a .json or a .txt because I'm getting a .txt? Or does it not matter? – BobZeBuilder Apr 18 '16 at 22:05
  • You can call it .BobZeBuilder if you like ;) – Padraic Cunningham Apr 18 '16 at 22:06
  • Hehe okay ;) Thanks :) – BobZeBuilder Apr 18 '16 at 22:07