Dictionaries, Files and Error Handling
Storing structured data, saving it between runs, and writing programs that survive things going wrong.
Dictionaries
A dictionary stores pairs of keys and values. You look a value up by its key rather than by a numeric position, which makes the code far more readable.
student = {
"name": "Aisha",
"year": 11,
"grades": [8, 9, 7],
"active": True
}
print(student["name"]) # Aisha
student["year"] = 12 # change a value
student["house"] = "Blue" # add a new pair
del student["active"] # remove a pairSafe access
print(student.get("email")) # None, no crash
print(student.get("email", "not given")) # a default value
print("name" in student) # TrueUsing square brackets on a missing key raises a KeyError. Use get() when the key might not be there.
Looping
for key in student:
print(key, "=", student[key])
for key, value in student.items():
print(f"{key}: {value}")
print(list(student.keys()))
print(list(student.values()))A list of dictionaries
This is how most real data is shaped.
students = [
{"name": "Aisha", "mark": 92},
{"name": "Ben", "mark": 68},
{"name": "Chloe", "mark": 77},
]
for s in students:
print(f"{s['name']:<8} {s['mark']}")
best = max(students, key=lambda s: s["mark"])
print("Top:", best["name"])
average = sum(s["mark"] for s in students) / len(students)
print(f"Average: {average:.1f}")Tuples and sets
point = (3, 7) # a tuple, cannot be changed
x, y = point # unpacking
unique = {1, 2, 2, 3, 3} # a set, duplicates removed
print(unique) # {1, 2, 3}- Each drawer has a key for a label and one value inside it.
- Assigning to a new key adds a drawer, here form: "10B".
- Ask for a value by its key, and in tests whether a key exists.
Files
Writing and reading
# Write, which ERASES the file first
with open("scores.txt", "w") as file:
file.write("Aisha,92\n")
file.write("Ben,68\n")
# Append, which adds to the end
with open("scores.txt", "a") as file:
file.write("Chloe,77\n")
# Read line by line
with open("scores.txt", "r") as file:
for line in file:
name, mark = line.strip().split(",")
print(f"{name} scored {mark}")
# Read the whole file
with open("scores.txt", "r") as file:
contents = file.read()
# Read into a list of lines
with open("scores.txt", "r") as file:
lines = file.readlines()with open(...) closes the file automatically, even if an error happens. Always use it.
| Mode | Meaning |
|---|---|
"r" | Read. Fails if the file does not exist. |
"w" | Write. Creates the file, and erases anything already in it. |
"a" | Append. Creates if needed, adds to the end. |
If a question or a program needs to add a record without losing existing data, the mode must be "a".
CSV files
import csv
with open("students.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["Name", "Mark"])
writer.writerow(["Aisha", 92])
with open("students.csv", "r") as file:
reader = csv.reader(file)
next(reader) # skip the header row
for row in reader:
print(row[0], row[1])JSON, for saving structured data
import json
data = {"players": ["Aisha", "Ben"], "high_score": 4200}
with open("save.json", "w") as file:
json.dump(data, file)
with open("save.json", "r") as file:
loaded = json.load(file)
print(loaded["high_score"])JSON is ideal for saving dictionaries and lists exactly as they are, which makes it the natural way to save game state or settings.
- r reads. The file is only looked at, and it must already exist.
- w writes. Everything already in the file is erased first.
- a appends. New lines go on the end and the old ones are kept.
- with closes the file for you, and \n ends a line.
Error handling
A robust program does not crash when something goes wrong. It handles it.
try:
age = int(input("Enter your age: "))
print(f"Next year you will be {age + 1}")
except ValueError:
print("That was not a whole number.")Handling several kinds of error
try:
with open("data.txt", "r") as file:
numbers = [int(line) for line in file]
print(sum(numbers) / len(numbers))
except FileNotFoundError:
print("The file does not exist yet.")
except ValueError:
print("The file contains something that is not a number.")
except ZeroDivisionError:
print("The file is empty.")
else:
print("Everything worked.")
finally:
print("Finished.")exceptruns when that error occurselseruns only if there was no errorfinallyruns either way, which is useful for tidying up
Errors worth catching
| Error | When |
|---|---|
ValueError | Converting text that is not a number |
FileNotFoundError | Opening a file that does not exist |
ZeroDivisionError | Dividing by zero |
KeyError | Looking up a key that is not in a dictionary |
IndexError | Using an index beyond the end of a list |
TypeError | An operation between incompatible types |
A bare except: hides real bugs, including typos in your own code, and makes problems far harder to find. Name the error you expect.
A robust input function
def get_int(prompt, low, high):
while True:
try:
value = int(input(prompt))
if low <= value <= high:
return value
print(f"Please enter a number between {low} and {high}.")
except ValueError:
print("That is not a whole number.")This function cannot crash and cannot return an invalid value. That is what robust means.
- try holds the code that might fail.
- except ValueError catches a bad conversion, such as int("abc").
- except ZeroDivisionError catches a division by zero.
- With no error, the except blocks are skipped and the program carries on.
Key terms you must be able to define
- Dictionary
- A collection storing pairs of keys and values, looked up by key.
- Key
- The name used to look up a value in a dictionary.
- Tuple
- An ordered collection that cannot be changed after creation.
- Set
- An unordered collection with no duplicate values.
- with open()
- A statement that opens a file and closes it automatically when finished.
- Append mode
- Opening a file so new data is added to the end without erasing existing content.
- CSV
- Comma separated values, a simple text format for tabular data.
- JSON
- A text format for storing structured data such as dictionaries and lists.
- Exception
- An error raised while a program is running.
- try and except
- A structure that attempts code and handles an error rather than crashing.
Flashcards
Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.
Reach the top grade on this topic
- Use a dictionary whenever data has named fields, rather than remembering list positions
- Always use
with open()so files close even when something fails - Choose append mode whenever existing data must be preserved
- Catch the specific exception you expect, never a bare except
- Write input functions that cannot crash and cannot return an invalid value
Mistakes that cost marks here
- Opening a file in write mode when the existing contents matter.
- Using square brackets on a dictionary key that might not exist, causing a KeyError.
- Using a bare except, which hides typos and real bugs.
- Forgetting to strip the newline character when reading lines from a file.
- Assuming a file exists, so the program crashes the very first time it runs.
Almost every real program you use stores its data as key and value pairs. Settings files, web APIs, save games and configuration are all essentially dictionaries.
Knowledge check
student.get("email") return if there is no email key?with open(...) rather than open(...)?finally do in a try block?except:?Exam-style questions
Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.
- LValues are accessed by a meaningful name rather than a numeric position
- LThis makes the code easier to read and less error prone
A dictionary lets each value be accessed by a meaningful key, so writing student["mark"] makes the intention completely clear. With a list you would have to remember that the mark happened to be stored at index 2, which is far harder to read and very easy to get wrong, especially if the order of the fields ever changes.
- LIt attempts code that might cause an error
- LIf the error occurs the except block runs instead of the program crashing
- LGives a valid example such as converting user input or opening a file
- LExplains that this makes the program robust
The try and except structure is used so a program can attempt something that might fail without crashing if it does. The code inside try is executed, and if it raises the named exception then Python jumps straight to the matching except block instead of stopping the program. A common example is converting user input to a number: if the user types letters, int() raises a ValueError, and without handling it the program would crash and the user would lose their work. With try and except the program can display a clear message and ask again. Handling errors like this is what makes a program robust, which means it continues to behave sensibly even when things go wrong.
- LUses try and except around the file operation
- LOpens the file for reading
- LConverts each line into a number
- LCalculates and prints the average
- LCatches FileNotFoundError with a helpful message
try: with open("marks.txt", "r") as file: marks = [int(line.strip()) for line in file if line.strip()] if len(marks) == 0: print("The file is empty.") else: print(f"Average: {sum(marks) / len(marks):.1f}") except FileNotFoundError: print("marks.txt could not be found.") except ValueError: print("The file contains something that is not a number.") Using with open means the file closes automatically. Each line is stripped of its newline before being converted, and blank lines are skipped. The empty file case is checked separately, because dividing by zero would otherwise raise its own error. Two specific exceptions are caught rather than using a bare except, so genuine bugs in the code are not hidden.
- LWrite mode erases everything already in the file
- LAppend mode adds to the end and keeps existing content
- LAppend should be used when previous data must be kept
Opening a file in write mode empties it completely before anything is written, so every piece of data previously stored in that file is lost. Opening in append mode leaves the existing contents intact and adds anything written to the end. Write mode is appropriate when the entire contents are being replaced, such as saving a complete updated set of records. Append mode is essential whenever a single new record is being added to data that must be preserved, for example writing one more high score to a file that already contains a list of them.
- LA bare except catches every error including ones you did not anticipate
- LThis hides genuine bugs such as typos in variable names
- LCatching a specific exception means only the expected problem is handled and other errors are still reported
A bare except catches absolutely every exception, including ones the programmer never anticipated. That means a genuine bug elsewhere in the try block, such as a misspelled variable name raising a NameError, is silently swallowed and replaced with whatever message the except block prints. The program appears to handle the situation while actually hiding a real fault, which can take a very long time to track down. Naming the specific exception, such as except ValueError, means only the problem you actually expected is handled, and any other error still stops the program and reports itself properly so it can be found and fixed.