Looking things up by name
A dictionary (dict) maps keys to values: {"AAPL": 184.5, "MSFT": 410.2}. Where a list finds an item by position, a dictionary finds it by key, and the lookup stays fast however many entries there are. prices["AAPL"] reads a value, prices["TSLA"] = 251.0 adds a new entry or replaces an existing one, and "AAPL" in prices asks whether a key exists. Reading a missing key with square brackets raises a KeyError.
prices = {"AAPL": 184.5, "MSFT": 410.2}
# Read, add and replace
print(prices["AAPL"]) # 184.5
prices["TSLA"] = 251.0
prices["AAPL"] = 185.0
print(prices) # {'AAPL': 185.0, 'MSFT': 410.2, 'TSLA': 251.0}
# in tests the keys; a missing key raises KeyError
print("MSFT" in prices, "GOOG" in prices) # True False
try:
prices["GOOG"]
except KeyError as error:
print("no such key:", error)get: a missing key is not an error
prices.get("GOOG", 0.0) returns the value, or the default you give when the key is missing (None if you give none), and never raises. That makes a counter easy: counts[symbol] = counts.get(symbol, 0) + 1 starts at 0 for a new symbol and adds one for each sighting. Use [] when a missing key would be a bug you want to hear about, and get when missing is normal.
# get() with a default never raises
prices = {"AAPL": 184.5}
print(prices.get("AAPL", 0.0)) # 184.5
print(prices.get("GOOG", 0.0)) # 0.0
print(prices.get("GOOG")) # None
# Counting with get
counts = {}
for symbol in ["AAPL", "MSFT", "AAPL", "AAPL"]:
counts[symbol] = counts.get(symbol, 0) + 1
print(counts) # {'AAPL': 3, 'MSFT': 1}Going through a dictionary
A plain for key in prices visits the keys. Use prices.values() for the values and prices.items() for (key, value) pairs, which you unpack in the loop: for symbol, price in prices.items(). Dictionaries remember the order in which keys were first added. Do not add or remove keys while looping over the same dictionary; loop over a copy or build a new dictionary instead.
# keys(), values() and items()
positions = {"AAPL": 200, "MSFT": 50, "GOOG": 0}
prices = {"AAPL": 184.5, "MSFT": 410.2}
for symbol in positions:
print(symbol, end=" ") # AAPL MSFT GOOG
print()
total = 0.0
for symbol, quantity in positions.items():
total += quantity * prices.get(symbol, 0.0)
print(round(total, 2)) # 57410.0Removing, merging and copying
prices.pop("AAPL", None) removes a key and returns its value (the default if it was missing); del prices["AAPL"] removes it and raises KeyError if it is absent. a.update(b) copies the entries of b into a, changing a; {**a, **b} or a | b builds a new dictionary in which b wins on shared keys. As with lists, copy = prices is only a second name: use dict(prices) or prices.copy() for a real copy.
# Removing and merging
book = {"AAPL": 200, "MSFT": 50}
sold = book.pop("MSFT", 0)
print(sold, book) # 50 {'AAPL': 200}
# {**a, **b} builds a new dict; b wins on shared keys
defaults = {"fee": 0.001, "currency": "USD"}
custom = {"fee": 0.0005}
settings = {**defaults, **custom}
print(settings) # {'fee': 0.0005, 'currency': 'USD'}
print(defaults) # unchanged: {'fee': 0.001, 'currency': 'USD'}What can be a key
A key must be hashable, which in practice means it cannot change: strings, numbers and tuples of those are fine, lists and dictionaries are not. Tuples make good compound keys, for example (symbol, venue). Values can be anything, including another list or dictionary. The same key can only appear once, so assigning to it again replaces the old value.
# A tuple as a compound key, a list as a value
best = {("AAPL", "NYSE"): 184.6, ("AAPL", "ARCA"): 184.5}
print(best[("AAPL", "ARCA")]) # 184.5
fills = {"AAPL": [100, 250], "MSFT": [75]}
fills["AAPL"].append(40)
print(fills) # {'AAPL': [100, 250, 40], 'MSFT': [75]}
# A list cannot be a key
try:
{["AAPL"]: 1}
except TypeError as error:
print("not hashable:", error)prices[symbol] says "this key must be here" and fails loudly if it is not. prices.get(symbol, default) says "missing is fine". Hiding a real bug behind a default is just as bad as crashing on a normal case, so decide which one you mean. The labs also check that the dictionaries you were given are not changed.d[key],d[key] = value,key in d: read, write, test a key.d.get(key, default): a safe lookup;d.items(): pairs to loop over.d.pop(key, default),del d[key],d.update(other),{**a, **b}: remove and merge.
Next up: sets, collections of unique values with fast membership tests and neat set arithmetic.
Lab · what's graded
- Keep the newest price per symbol with
latest_prices25 pts - Count how often each symbol appears with
count_symbols25 pts - Value a portfolio from two dictionaries with
portfolio_value25 pts - Combine two position books with
merge_positions25 pts