Problem saving values to a text file

I have a problem with saving values to file using write(), essentially the program accepts 3 numerical values then stores them as strings in a textfile labeled “scores.txt”. I believe I messed up somewhere in the code itself, since the only thing saved to the textfile is “None” in the place of numbers. Then when the program attempts to read the file it stops with a runtime error. I have searched as best as I can, but maybe I’m not wording it right or something. I have also tried deleting and rewriting the program from scratch twice, but no dice, same problem. My only conclusion, is that I’ve done something incorrectly multiple times in a row, and I still haven’t caught it. This is my first time working with write() & read(), as I continue to learn python, so any help is appreciated. Picture is below (I would put more, but there is a limit):

The code

def main():
	write_to_file = open('scores.txt', 'w')
	
	for i in range(3):
		score = get_input(0, 100, '\nEnter a score between 0 and 100: ')
		write_to_file.write(str(score) + "\n")

	write_to_file.close()

	#read from a file
	read_from_file = open('scores.txt', 'r')
	scores = []
	for line in read_from_file:
		line = line.replace('\n', '')
		scores.append(int(line))

	read_from_file.close()
	print(scores)
	print('Average:', sum(scores) / len(scores))

def get_input(min, max, prompt):
	while True:
		user = int(input(prompt))
		if user < min or user > max:
			print('Invalid entry. Please re-enter: ')
			continue
		else:
			break
	return user

main()

The output:

Enter a score between 0 and 100: 45

Enter a score between 0 and 100: 65

Enter a score between 0 and 100: 76

Traceback:
     File "main.py", line 31
          main()
     File "main.py", line 15, in main
          scores.append(int(line))
ValueError: invalid literal for int() with base 10: 'None'

What the savefile should look like:

45
65
76

What the savefile actually looks like:

None
None
None

Welcome @Qwerty ! Thanks for the detailed output, error/traceback, and expected/actual behavior, as well as the detailed description. These details are very helpful in helping us help you. One thing thing, though… Please do not upload images of code/errors when asking a question.. Instead, please include your code between triple backticks, so we can better read, test, format and experiment with it, like this:

```python
YOUR CODE HERE
```

Also, as a tip, your code is otherwise relatively well formatted, but its a good idea to always use 4-space indents, the standard and near universal convention in Python, as it makes your code much easier to read—especially combined with the screenshot, its hard for my eye to pick out indentation levels and easy to make mistakes on that front.

As a matter of fact, a careful look at indentation reveals that is exactly your problem (a common source of error for newer, and sometimes even experienced Python developers). The line return user is inside the while loop of get_input, and as such can never execute—either the if branch triggers and the loop continues, bypassing the line, or the else branch triggers and it breaks, also bypassing it. This means the function implicitly returns None once the while loop breaks (in Python, functions return None if they don’t have an explicit return statement), which then gets assigned to score, and is then written to the file and read back in, causing your issue. While you likely intend is to outdent it one level, so it executes after the while loop breaks and a valid number has been read it, which will fix your issue.

To prevent that in the future, using four space indents makes this more clear to your eye, as does carefully following your editor’s intent guides. Good static analysis tools, like Pylint, will also catch this error in multiple places—at a minimum, they will flag return user as being unreachable code, and then flag score = get_input() for assigning the result of a function without a return value, which are both usually signs of errors (as they are here), and potentially also point out that get_input() has inconsistent return statements, where some but not all branches of the function return a value (also generally a sign of an error).

I have a few other comments on potential issues with your code, but I’ll make those in a separate message to avoid belaboring this one.

Please always paste the code into the message, as you have with the
output. Those of us on email have to (tediously) go to the web to see
the code and also cannot copy/paste from it, because it is an image. The
vision impaired are also affected by screenshots.

Is your output really made by exactly the code you have screenshotted? I
would expect your savefile output if get_input() were returning None but
I don’t see how it can do that with the code as it is presented.

Put a print() call in your “for i in range…” loop to show what the
value of user is.

Also, a cleaner way to write your write code is:

with open('scores.txt', 'w') as f:
    for i in range(3):
       ... input stuff ...
       print(score, file=f)

instead of laboriously composing a text line with write(). The above
also closes the file for you. Same structure for reading from the file.

Cheers,
Cameron Simpson cs@cskk.id.au

Hey, not sure if you can see my reply (maybe something with your email-based method of doing so?) but if you look carefully (especially easy to miss due to the 2-space indents and the screenshot format, and happens to all of us regardless), the issue is with them indenting the return statement of get_input a level too deep, which results in the function returning None.

Here are a few other comments on your code; some are potential issues, and other are more informative. From most basic/essential to more advanced/refinements:

  • As @cameron also suggests (see his reply for an example), you should always use with statements when opening files. While your code is careful to close() them after, it is easy in more complex programs to have code paths that neglect this, and this also makes sure it happens even after an error is raised—as is indeed the case. Plus, less work for you to manage and keep track of.

  • In addition, its usually a good idea to keep the file open for a short a time as practical, and avoid lots of little reads/writes to the same file if you can avoid it, as this helps your program be more efficient, perform better and reduces the chance that something else might touch the file. So instead of opening your file beforehand, waiting for the user’s input and then writing one number on each pass of the loop, I’d suggest assembling your scores into a list, using str.join() to build it into a string, and then writing it all to the file in one go, like this:

    scores = []
    for i in range(3):
        score = get_input(...)
        scores.append(str(score))
    with open('scores.txt', 'w') as outfile:
        outfile.write('\n'.join(scores) + '\n')
    

    You could do the same thing reading input, though if your file is long very long, reading it line by line is slower but more memory efficient, and your current approach is more optimal:

    with open('scores.txt') as infile:
        raw_scores = infile.read().splitlines()
    scores = []
    for raw_score in raw_scores:
        scores.append(int(raw_score))
    

    Also, using splitlines() takes care of the \ns for you. With your approach, a cleaner/simpler way of doing that would be calling .rstrip() on your read-in string instead of a string replace.

  • As a refinement to the above, this is a great use case for a list comprehension, which both simplifies your code and is more efficient, using one line instead of four. Here’s how that would look for writing:

    scores = [str(get_input(...)) for i in range(3)]
    with open(...):
        # Write as before
    

    And reading:

    open(...):
        # Read as before
    scores = [int(raw_scores) for raw_score in raw_scores]
    
  • On a minor note, when writing, the loop variable is unused, so by convention, you should use __ instead of i as a dummy var to clearly indicate this (_ is a common alternative, but it is also used by gettext and as the most recent result from the interpreter, so I find it best to always use __ for clarity).

  • On a bit of a more advanced note, when reading or writing a file in text mode (no b), it is a good idea to always specify your encoding, unless you are sure you need the default behavior. Otherwise, your OS and environment-specific default is used, which may not be the standard UTF-8, may not support non-ASCII characters and may result in inconsistent results between platforms and environments.

    It doesn’t really matter here, since you are just writing numbers, but its not a bad idea to get in the habit of always specifying encoding='utf-8' when using open() unless you know you need something else to avoid accidentally not including it when its important. Python 3.10+ has an optional warning (that I played a small part in helping add) that warns for this, and it may eventually warn or error by default. See PEP 597 for details, if you’re curious.

Cheers, and best of luck!

Hey, not sure if you can see my reply (maybe something with your email-based method of doing so?)

Email’s fine provided your reply is text. It is screenshots that don’t
come through, necessitating a visit to the web forum.

but if you look carefully (especially easy to miss due to the 2-space
indents and the screenshot format, and happens to all of us
regardless),

I use 2-space indents in my personal code. Alas, the OP has replaced the
screenshot with new code. I can’t check what I saw before :frowning:

I may indeed have misread the indentation of his return; I thought it
was correct but may have misread it.

Cheers,
Cameron Simpson cs@cskk.id.au

Understood; I imagine the email for my post hadn’t come in to you yet, since I only submitted it less than 10 minutes before yours. No worries. And yeah, one more reason to not post code as text; I have a more or less canned response I use for this situation (linking to the meta.SO answer that I’ve seen others here do too) given how unfortunately common it is (assuming the user provides code at all), as I imagine you do too.

Personally, I find making and missing these sorts of errors much easier with only two-space indents, since indentation is so critical in Python, as well as prefer to adopt one consistent convention rather than switching back and forth between private and public projects, but to each their own.

Seems like they are actually using tabs :confused: and have their editor set to convert that to two spaces, at least judging by what they posted, and the error is still there (and clearer now, though even with four spaces we all miss it sometimes). I only saw the image before, but I can verify that the error was present there as well, which exactly explains the behavior they are getting.

Thank you for your in depth response, in hindsight it seems more obvious to have checked the line spacing more closely. That could have saved me a lot of headache. Also, thanks for expanding further on things I didn’t know. I have fixed the problem, and changed my indentation to 4 spaces.

1 Like