Lookup by name instead of by number#
A list finds things by position. A dictionary finds them by a key you choose:
item = {"name": "Water", "amount": 10}
print(item["name"])
item["amount"] = 20
print(item)
Water
{'name': 'Water', 'amount': 20}
Same square brackets as a list, but what goes inside is a key rather than an index — item["name"], never item[0]. Assigning to a key that does not exist adds it; assigning to one that does replaces its value. There is no separate "add" operation.
Keys are usually strings, and they must be immutable — which rules out using a list as a key:
d = {}
d[["a"]] = 1
Traceback (most recent call last):
File "d.py", line 2, in <module>
d[["a"]] = 1
~^^^^^^^
TypeError: unhashable type: 'list'
"Unhashable" is Python's word for "this could change underneath me, so I cannot use it to file things by." A tuple works; a list does not.
KeyError, and the two ways to avoid it#
item = {"name": "Water"}
print(item["price"])
Traceback (most recent call last):
File "k.py", line 2, in <module>
print(item["price"])
~~~~^^^^^^^^^
KeyError: 'price'
The key is missing. In real code the cause is almost always a typo, a case mismatch — "Name" is a different key from "name" — or one item in a collection built with a different shape from the rest.
item = {"name": "Water"}
print(item.get("price"))
print(item.get("price", 0))
print("name" in item)
None
0
True
Use the brackets when a missing key means your program is wrong and you want to hear about it. Use .get() when a key is legitimately optional. in asks about keys, never values.
Looping over a dictionary#
d = {"one": 1, "two": 2}
print(list(d.keys()))
print(list(d.values()))
for k, v in d.items():
print(k, v)
['one', 'two']
[1, 2]
one 1
two 2
.items() hands you both at once, which saves a lookup inside the loop. Since Python 3.7 a dictionary keeps its insertion order — but if you need a particular order, ask for it:
roster = {"Jill": "A", "Jack": "B"}
for k in sorted(roster.keys()):
print(k, roster[k])
Jack B
Jill A
d = {"a": 1, "b": 2}
del d["a"]
print(d, len(d))
{'b': 2} 1
The counting idiom#
This is the single most reused dictionary pattern, and it is worth memorising as one line:
words = ["a", "b", "a", "c", "a"]
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts)
{'a': 3, 'b': 1, 'c': 1}
Read it right to left: get the current count or zero if this is the first time, add one, store it back. The 0 default is what makes the first sighting of an item work without a special case — write it with plain brackets and the first counts[w] + 1 raises KeyError.
A list of dictionaries#
Records with named fields, in order — the shape most small data sets take:
supplies = [{"name": "Water", "amount": 1}, {"name": "Radio", "amount": 2}]
for it in supplies:
print(it["name"], it["amount"])
Water 1
Radio 2
The loop hands you one dictionary per pass; the keys read its fields. Every record needs the same keys — one item missing "amount" stops the loop with a KeyError partway through, after some output has already been printed.
Reading and writing files#
Use with. It closes the file for you, including when something raises partway through:
with open("demo.txt", "w") as f:
f.write("first\nsecond\n")
with open("demo.txt") as f:
print(f.read())
first
second
Note that .write() does not add a newline — if you want lines, you write the \n yourself.
| Mode | What it does |
|---|---|
"r" | read; the default, and an error if the file does not exist |
"w" | write; empties the file immediately, or creates it |
"a" | append; adds to the end, or creates it |
"w" truncates the file the moment it is opened, before your first write. Open a file for writing to "check something" and its contents are already gone. There is no warning and no undo.
with open("demo.txt", "w") as f:
f.write("first\n")
with open("demo.txt", "w") as f:
f.write("second\n")
with open("demo.txt") as f:
print(f.read())
second
Same code with "a" on the second open keeps both:
with open("demo.txt", "w") as f:
f.write("first\n")
with open("demo.txt", "a") as f:
f.write("second\n")
with open("demo.txt") as f:
print(f.read())
first
second
Reading line by line, and the extra blank lines#
Looping over an open file gives you one line at a time — and each one arrives with its newline still attached:
with open("demo.txt", "w") as f:
f.write("first\nsecond\n")
with open("demo.txt") as f:
for line in f:
print(repr(line))
'first\n'
'second\n'
repr() is the trick that shows it. Because the line already ends in a newline and print adds another, output comes out double-spaced. .strip() fixes it:
with open("demo.txt", "w") as f:
f.write("first\nsecond\n")
with open("demo.txt") as f:
for line in f:
print(line.strip())
first
second
.readlines() gives the whole file as a list in one go — convenient for a small file, and the newlines are still there:
with open("demo.txt", "w") as f:
f.write("first\nsecond\n")
with open("demo.txt") as f:
print(f.readlines())
['first\n', 'second\n']
Combine that with .split() and you can read a data file into fields: loop the lines, strip each one, split it on the separator, convert what needs converting.
Exceptions#
A missing file stops the program:
f = open("nope.txt")
Traceback (most recent call last):
File "o.py", line 1, in <module>
f = open("nope.txt")
^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'nope.txt'
try/except lets you carry on instead:
try:
f = open("nope.txt")
except FileNotFoundError:
print("Could not open the file.")
Could not open the file.
Name the exception you expect. A bare except: swallows everything — including the typo in the line below it, which then produces no error and no output, and leaves you debugging a program that quietly does nothing.
Catching the exception object gives you the message:
try:
n = int("hi")
except ValueError as e:
print("ValueError:", e)
ValueError: invalid literal for int() with base 10: 'hi'
That is the exception behind every int(input()) on a user who typed a word, which makes it the one worth handling in a first course.
The full form has four parts:
def divide(x, y):
try:
result = x // y
except ZeroDivisionError:
print("cannot divide by zero")
else:
print("answer is", result)
finally:
print("done")
divide(10, 2)
divide(10, 0)
answer is 5
done
cannot divide by zero
done
else runs only when nothing was raised. finally runs either way — note done appears under both calls. Keep the try block down to the line that can actually fail and put the rest in else; a large try hides which line you were guarding against.
| Exception | Typically means |
|---|---|
KeyError | a dictionary key that is not there — often a typo or wrong case |
IndexError | a list or string position past the end |
ValueError | right type, impossible value — int("hi") |
TypeError | wrong type for the operation — "I am " + 20 |
NameError | a name that has not been defined — usually a misspelling |
FileNotFoundError | the path is wrong, or you are not in the directory you think |
ZeroDivisionError | a divisor that reached zero |
Where these go wrong#
- Indexing a dictionary by number.
item[0]looks for a key0, not the first entry. - Case mismatch in a key.
"Name"and"name"are different keys. A very commonKeyError. counts[w] + 1on the first sighting.KeyError. Usecounts.get(w, 0) + 1.- Opening for writing to inspect a file.
"w"empties it on open. Use"r", or"a". - Forgetting
.strip()on file lines. Double-spaced output, and comparisons that fail because of an invisible\n. - Expecting
.write()to add newlines. It does not. Write\nyourself. - A bare
except:. Hides real bugs, including typos on the very next line. - A
tryblock wrapping half the program. You lose track of which line you were guarding. - Assuming every record has the same keys. One odd item raises partway through, after output has already been printed.
More Python guides
Free study guides for a first Python course — every listing executed before publication.
See the Python course →Frequently asked questions#
What is a KeyError in Python?
It means you asked a dictionary for a key it does not hold. Usually the cause is a typo or a case mismatch — "Name" is not "name" — or an item built with a different shape from the others. Use .get(key, default) or the in operator when a key may legitimately be missing.
What is the difference between d[key] and d.get(key)?
d[key] raises KeyError when the key is absent. d.get(key) returns None instead, and d.get(key, default) returns whatever default you supply. Use the brackets when a missing key is a bug, and .get() when it is expected.
How do I count things in Python?
Use a dictionary and the .get() idiom: counts[item] = counts.get(item, 0) + 1. It reads the current count or starts from zero, adds one, and stores it back — so it works on the first sighting of an item as well as the hundredth.
Why did opening a file for writing delete everything in it?
Because mode "w" truncates the file to empty the moment it is opened, before you write anything. If you want to add to a file rather than replace it, open it with "a" for append.
Why do my printed file lines have blank lines between them?
Because each line read from a file still carries its trailing newline character, and print adds another. Use line.strip() to remove it, or print(line, end='').
What is the difference between else and finally in try/except?
The else block runs only when no exception was raised. The finally block runs either way, exception or not, which makes it the place for cleanup that must happen regardless.
