Files and exceptions¶
~25 min · prerequisite: Modules and imports
What you will be able to do¶
- Write text to a file and read it back, using
with open(...). - Catch an error with
tryandexcept, so the program can keep running. - Practise file reading and writing in a runnable block, using
io.StringIO.
The idea¶
A file is a named collection of data stored on your disk. A text file holds characters, such as lines of words. Python works with files through a file object, which you get from the built-in function open. The first argument is the file name, and the second is the mode. The mode "r" means read, "w" means write and replaces any old contents, and "a" means append to the end.
Use the with statement, with open(...) as file:, and put the work on the file in the indented lines. When the block ends, Python closes the file, even if an error happened inside. You should always pass encoding="utf-8" for text, so that letters such as é are stored the same way on every computer.
Writing to a file is different from print. The write method takes one string, and it does not add a newline at the end. If you want separate lines, write "\n" yourself. Reading gives you the text back. You can loop over the file with for line in file, and each line keeps its newline at the end.
Errors are called exceptions. When Python meets an error it cannot handle, it stops the program and shows a traceback. You can catch an exception with a try block. The code inside try runs first. If it raises a matching exception, the code under except runs instead of stopping the program. Name the exception you expect, such as ValueError, so that you do not hide other problems.
Runnable blocks in this course cannot create real files, so the file examples below are plain code. The io.StringIO class gives you an in-memory file that behaves like a text file. You can write to it, then read it back in the same way.
Worked example¶
This block writes two lines to a file named notes.txt, then reads them back. Run it in an empty folder.
with open("notes.txt", "w", encoding="utf-8") as file:
file.write("first line\n")
file.write("second line\n")
with open("notes.txt", encoding="utf-8") as file:
for line in file:
print(line.rstrip("\n"))
Expected output:
Line by line, this is what Python does:
open("notes.txt", "w", encoding="utf-8")creates the file, or replaces it if it exists, and opens it for writing.- Each
file.writecall adds one line, and the\nat the end makes the next text start on a new line. - The first
withblock ends, and Python closes the file. The text is now saved on disk. - The second
openuses the default mode"r", which means read. for line in filegives one line at a time. Each line still ends with a newline.line.rstrip("\n")removes that newline, soprintdoes not add an extra blank line.
Try it¶
The first block writes and reads with io.StringIO. The second block loops over lines. The third block uses try and except to handle bad input.
Common mistakes¶
-
Opening a file that does not exist. The following code reads a file that is not in the folder:
Python reports:
Check the file name and the folder you run the script from.
-
Writing a number instead of a string. The following code passes an
inttowrite:Python reports:
Convert the value first, with
file.write(str(42)). -
Forgetting the newline. The following code runs without an error, but the two lines run together:
The file now holds
firstsecondon one line. Add"\n"to the end of each line you write. -
Using a file after its
withblock. The following code reads the file after it is closed:Python reports:
Do all the reading inside the
withblock, and store the result in a variable.
Practice¶
- Create an
io.StringIOobject, write the word"hello"to it, and assert thatgetvalue()equals"hello". - Define
count_lines(text)that usesio.StringIO(text)and counts the lines with aforloop. Assert thatcount_lines("one\ntwo\nthree\n")equals3. - Define
safe_int(text, default=0)that returnsint(text), ordefaultwhen the conversion raisesValueError. Assert thatsafe_int("12")equals12, thatsafe_int("x")equals0, and thatsafe_int("x", default=-1)equals-1. - Define
total_score(source)wheresourceis a file-like object with lines such as"Ada 90". Return the sum of the numbers. Assert thattotal_score(io.StringIO("Ada 90\nGrace 85\n"))equals175. - Stretch: define
average(numbers)that raisesValueError("no numbers")for an empty list, and otherwise returns the mean. Definesafe_average(numbers)that returnsNonewhenaverageraises that error. Assert thatsafe_average([2, 4])equals3.0, and thatsafe_average([])isNone.
Solution
- ```python import io
buffer = io.StringIO() buffer.write("hello") assert buffer.getvalue() == "hello" ```
- ```python import io
def count_lines(text): total = 0 for line in io.StringIO(text): total = total + 1 return total
assert count_lines("one\ntwo\nthree\n") == 3 ```
- ```python def safe_int(text, default=0): try: return int(text) except ValueError: return default
assert safe_int("12") == 12 assert safe_int("x") == 0 assert safe_int("x", default=-1) == -1 ```
- ```python import io
def total_score(source): total = 0 for line in source: name, score = line.split() total = total + int(score) return total
assert total_score(io.StringIO("Ada 90\nGrace 85\n")) == 175 ```
- ```python def average(numbers): if not numbers: raise ValueError("no numbers") return sum(numbers) / len(numbers)
def safe_average(numbers): try: return average(numbers) except ValueError: return None
assert safe_average([2, 4]) == 3.0 assert safe_average([]) is None ```
Recap¶
with open(name, mode, encoding="utf-8") as file:opens a file and closes it when the block ends."w"writes, and"r"reads.writedoes not add a newline, andfor line in filekeeps the newline at the end of each line.tryandexcept ValueErrorcatch a specific error, so the program can handle it and continue.