Files and Exceptions
Learning to work with files and save data will make your programs more relevant and useful. Users will be able to choose what data to enter and when to enter it, and they can use your programs and quit whenever they want, picking up where they left off later.
Reading from a File
Reading an Entire File
To work with the contents of a file, you first need to read the file into memory. The open()
function takes one argument: the name of the file you want to open.
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
The keyword with
closes the file once access to it is no longer needed. You could open and close the file by calling open()
and close()
, but if a bug in your program prevents the close()
statement from executing, the file may never close.
File Paths
When you pass a simple filename like 'pi_digits.txt'
to the open()
function, Python looks in the directory where the program being executed is stored.
Relative and Absolute File Paths
# Relative path
with open('text_files/filename.txt') as file_object:
# Absolute path (Linux/macOS)
file_path = '/home/ehmatthes/other_files/text_files/filename.txt'
with open(file_path) as file_object:
# Absolute path (Windows)
file_path = 'C:\Users\ehmatthes\other_files\text_files\filename.txt'
with open(file_path) as file_object:
Windows systems use a backslash (\
) instead of a forward slash (/
) when displaying file paths, but you can use forward slashes in your code even on Windows systems.
Reading Line by Line
filename = 'pi_digits.txt'
with open(filename) as file_object:
for line in file_object:
print(line)
This might produce extra blank lines because each line in the text file ends with a newline character, and the print()
function adds its own newline. You can remove these extra blank lines:
filename = 'pi_digits.txt'
with open(filename) as file_object:
for line in file_object:
print(line.rstrip())
Making a List of Lines from a File
filename = 'pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
The readlines()
method takes each line from the file and stores it in a list, which is returned to you.
Working with a File's Contents
filename = 'pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string)
print(len(pi_string))
Output:
3.1415926535897932384626433832795
32
Large Files: One Million Digits
When Python reads from a text file, it interprets all text in the file as a string. Here's how you might work with a very large file:
filename = 'pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(f"{pi_string[:52]}...")
print(len(pi_string))
Is Your Birthday Contained in Pi?
filename = 'pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")
Writing to a File
Writing to an Empty File
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
The second argument, 'w'
, tells Python that you want to open the file in write mode. You can open a file in:
- read mode (
'r'
) - write mode (
'w'
) - append mode (
'a'
) - read and write mode (
'r+'
)
Opening a file in write mode ('w'
) will erase the contents of the file before returning the file object.
Writing Multiple Lines
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.\n")
file_object.write("I love creating new games.\n")
Appending to a File
filename = 'programming.txt'
with open(filename, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in a browser.\n")
Exceptions
Python uses special objects called exceptions to manage errors that arise during a program's execution. Whenever an error occurs that makes Python unsure what to do next, it creates an exception object.
Handling the ZeroDivisionError Exception
print(5/0)
This will produce:
ZeroDivisionError: division by zero
Using try-except Blocks
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
Output:
You can't divide by zero!
Using Exceptions to Prevent Crashes
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
try:
answer = int(first_number) / int(second_number)
except ZeroDivisionError:
print("You can't divide by zero!")
else:
print(answer)
The else Block
try:
answer = int(first_number) / int(second_number)
except ZeroDivisionError:
print("You can't divide by zero!")
else:
print(answer)
The else
block will run only if the try
block was successful.
Handling the FileNotFoundError Exception
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
Analyzing Text
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
# Count the approximate number of words in the file.
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
The split()
method creates a list of words from a string.
Working with Multiple Files
def count_words(filename):
"""Count the approximate number of words in a file."""
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
Failing Silently
Sometimes you want your program to fail silently when an exception occurs and continue as if nothing happened. You can use the pass
statement:
def count_words(filename):
"""Count the approximate number of words in a file."""
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
pass
else:
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
Deciding Which Errors to Report
How do you know when to report an error to your users and when to fail silently? If users know which files are supposed to be analyzed, they might appreciate a message informing them why some files were not analyzed. If users expect to see some results but don't know which files are supposed to be processed, they might not need to know that some files were unavailable.
Storing Data
Many of your programs will ask users to input certain kinds of information. The json
module allows you to dump simple Python data structures into a file and load the data from that file the next time the program runs.
Using json.dump() and json.load()
import json
numbers = [2, 3, 5, 7, 11, 13]
filename = 'numbers.json'
with open(filename, 'w') as f:
json.dump(numbers, f)
Now let's write a program that loads the list back into memory:
import json
filename = 'numbers.json'
with open(filename) as f:
numbers = json.load(f)
print(numbers)
Output:
[2, 3, 5, 7, 11, 13]
Saving and Reading User-Generated Data
import json
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
Now let's write a program that greets a user whose name has already been stored:
import json
filename = 'username.json'
with open(filename) as f:
username = json.load(f)
print(f"Welcome back, {username}!")
Combining the Programs
import json
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
Refactoring
Often, you'll come to a point where your code will work, but you'll recognize that you could improve the code by breaking it up into a series of functions:
import json
def get_stored_username():
"""Get stored username if available."""
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
return None
else:
return username
def get_new_username():
"""Prompt for a new username."""
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
return username
def greet_user():
"""Greet the user by name."""
username = get_stored_username()
if username:
print(f"Welcome back, {username}!")
else:
username = get_new_username()
print(f"We'll remember you when you come back, {username}!")
greet_user()
Each function in this final version has a single, clear purpose. This compartmentalized work makes your code easier to write, read, test, and maintain.
Summary
In this chapter you learned how to:
- Read entire files and read files line by line
- Work with absolute and relative file paths
- Write text to files using write mode and append mode
- Use try-except blocks to handle exceptions gracefully
- Store Python data structures using the JSON format
- Refactor code to make it cleaner and easier to extend
Files and exception handling will help you work with data from files and handle errors that might occur in your programs, making your programs more robust and user-friendly.