Les 6
Dictionaries in de praktijk
Dictionaries nesten, tellen en groeperen.
🎯 Na deze les kun je
- geneste dictionaries lezen en aanpassen
- tellen met Counter en groeperen met defaultdict
- dictionaries sorteren op waarde
Een dictionary koppelt sleutels aan waarden: een productcode aan een prijs, een gebruikersnaam aan een profiel, een woord aan het aantal keer dat het voorkomt. Je kent de basis al uit Programmeerconcepten. In de praktijk krijg je te maken met dictionaries in dictionaries (bijvoorbeeld JSON uit een web-API, les 15 en 25), en met tellen en groeperen. Daarvoor heeft Python twee handige hulpmiddelen: Counter en defaultdict.
Snel herhalen
prijzen = {"appel": 0.50, "peer": 0.65}
prijzen["kiwi"] = 0.30 # toevoegen of vervangen
print(prijzen["appel"])
print(prijzen.get("mango")) # None als de sleutel ontbreekt
print(prijzen.get("mango", 0.0)) # of een eigen standaardwaarde
print("peer" in prijzen)
for product, prijs in prijzen.items():
print(f"{product}: {prijs:.2f}")
0.5
None
0.0
True
appel: 0.50
peer: 0.65
kiwi: 0.30
prijzen["mango"] zou een KeyError geven. Gebruik get als een sleutel mag ontbreken, en [] als hij er moet zijn (dan wil je juist een fout als dat niet zo is).
Een dictionary onthoudt de volgorde waarin sleutels zijn toegevoegd.
Geneste structuren
Data van buiten heeft vaak meerdere niveaus:
bestelling = {
"id": 1042,
"klant": {"naam": "Noor Jansen", "plaats": "Utrecht"},
"regels": [
{"product": "appel", "aantal": 6, "prijs": 0.50},
{"product": "kaas", "aantal": 1, "prijs": 7.95},
],
}
print(bestelling["klant"]["naam"])
print(bestelling["regels"][1]["product"])
totaal = 0
for regel in bestelling["regels"]:
totaal += regel["aantal"] * regel["prijs"]
print(f"Totaal: {totaal:.2f}")
Noor Jansen
kaas
Totaal: 10.95
Lees een pad als bestelling["regels"][1]["product"] van links naar rechts: de dictionary bestelling, daarin de lijst regels, daarin het element met index 1 (een dictionary), daarin de sleutel product.
Is een tussenliggend niveau optioneel, dan kun je get aaneenschakelen:
bestelling = {"id": 7, "klant": {"naam": "Ali"}}
print(bestelling.get("klant", {}).get("plaats", "onbekend"))
onbekend
Tellen met Counter
Tellen hoe vaak iets voorkomt is zo gewoon dat Python er een eigen type voor heeft:
from collections import Counter
tekst = "de kat zat op de mat en de kat sliep"
telling = Counter(tekst.split())
print(telling["de"])
print(telling["hond"])
print(telling.most_common(2))
3
0
[('de', 3), ('kat', 2)]
Een Counter is een dictionary die voor onbekende sleutels 0 teruggeeft in plaats van een KeyError. most_common(n) geeft de n vaakst voorkomende waarden. Je kunt hem ook bijwerken:
from collections import Counter
statussen = Counter()
for code in [200, 404, 200, 500, 200, 404]:
statussen[code] += 1
print(statussen)
Counter({200: 3, 404: 2, 500: 1})
Groeperen met defaultdict
Wil je dingen in groepen verdelen, dan moet je bij een gewone dictionary steeds controleren of de groep al bestaat. defaultdict doet dat voor je:
from collections import defaultdict
leerlingen = [("Sam", "4A"), ("Noor", "4B"), ("Ali", "4A"), ("Lisa", "4B"), ("Tim", "4A")]
per_klas = defaultdict(list)
for naam, klas in leerlingen:
per_klas[klas].append(naam)
print(dict(per_klas))
{'4A': ['Sam', 'Ali', 'Tim'], '4B': ['Noor', 'Lisa']}
defaultdict(list) maakt automatisch een lege lijst als je een sleutel gebruikt die er nog niet is. Andere varianten: defaultdict(int) begint bij 0 (tellen), defaultdict(set) bij een lege set.
Zonder defaultdict schrijf je hetzelfde met setdefault:
per_klas = {}
for naam, klas in [("Sam", "4A"), ("Ali", "4A")]:
per_klas.setdefault(klas, []).append(naam)
print(per_klas)
{'4A': ['Sam', 'Ali']}
Een defaultdict maakt ook een sleutel aan als je hem alleen leest: per_klas["5C"] voegt "5C": [] toe. Wil je controleren of een sleutel bestaat, gebruik dan "5C" in per_klas.
Uitgewerkt voorbeeld: een logbestand samenvatten
Per IP-adres willen we weten hoeveel verzoeken er waren en welke paden het opvroeg. Voorspel de uitvoer.
from collections import Counter, defaultdict
log = """\
10.0.0.1 GET /index.html 200
10.0.0.2 GET /contact 404
10.0.0.1 GET /over 200
10.0.0.3 POST /login 500
10.0.0.1 GET /index.html 200
"""
per_ip = Counter()
paden = defaultdict(set)
for regel in log.splitlines():
ip, methode, pad, status = regel.split()
per_ip[ip] += 1
paden[ip].add(pad)
for ip, aantal in per_ip.most_common():
print(f"{ip:<10} {aantal} {sorted(paden[ip])}")
10.0.0.1 3 ['/index.html', '/over']
10.0.0.2 1 ['/contact']
10.0.0.3 1 ['/login']
paden is een defaultdict(set), zodat een pad dat twee keer voorkomt maar één keer telt.
Veelgemaakte fouten
KeyErrorbij een sleutel die soms ontbreekt; gebruikget.- Een dictionary veranderen terwijl je eroverheen loopt:
RuntimeError: dictionary changed size during iteration. Loop over een kopie:for k in list(d):. - Een lijst als sleutel (les 5):
TypeError: unhashable type: 'list'. - Met een
defaultdictper ongeluk lege sleutels aanmaken door ze op te vragen.
Oefenen
Oefening 1. Tel hoe vaak elke letter voorkomt in "mississippi" en print de drie meest voorkomende.
Modelantwoord oefening 1
from collections import Counter
print(Counter("mississippi").most_common(3))
[('i', 4), ('s', 4), ('p', 2)]
Een string is een reeks tekens, dus Counter telt per letter.
Oefening 2. Groepeer de woorden ["appel", "ananas", "banaan", "bes", "citroen"] op hun eerste letter.
Modelantwoord oefening 2
from collections import defaultdict
groepen = defaultdict(list)
for woord in ["appel", "ananas", "banaan", "bes", "citroen"]:
groepen[woord[0]].append(woord)
print(dict(groepen))
{'a': ['appel', 'ananas'], 'b': ['banaan', 'bes'], 'c': ['citroen']}
Oefening 3. Gegeven de bestelling uit het voorbeeld hierboven: schrijf een expressie die de plaats van de klant geeft, en "onbekend" als klant of plaats ontbreekt.
Modelantwoord oefening 3
bestelling = {"id": 1042, "klant": {"naam": "Noor Jansen"}}
print(bestelling.get("klant", {}).get("plaats", "onbekend"))
onbekend
Lab: woordfrequenties en een index
- In
boek.txtstaat een kort verhaal. - Schrijf
woordtelling(tekst)die eenCounterteruggeeft van alle woorden in kleine letters, zonder leestekens (.,!?;:"'). - Schrijf
index(tekst)die per woord een gesorteerde lijst van regelnummers (vanaf 1) teruggeeft waarop het voorkomt. Gebruikdefaultdict(set). - Schrijf
top(tekst, n)die de n meest voorkomende woorden van minstens 4 letters teruggeeft. - Controleer met
beetje check python-les6.
Samenvatting
d[k]voor sleutels die er moeten zijn,d.get(k, standaard)voor sleutels die mogen ontbreken;items()om over sleutels en waarden te lopen.- Geneste data lees je stap voor stap:
data["regels"][0]["product"]. Countertelt en heeftmost_common.defaultdict(list)endefaultdict(set)groeperen zonder steeds te controleren of de groep al bestaat.
In les 7 schrijf je lijsten, sets en dictionaries in één regel met comprehensions.
Lab
Woordfrequenties in een tekst tellen
💻 Dit lab doe je op je eigen computer met Docker. Start de lab-container met (geeft de opdracht een eigen startcommando, gebruik dan dat):
$ docker run -it --rm registry.wizg.it/webhosting/beetje-labs:python-les6
Klaar? Controleer je werk met:
$ beetje check python-les6
📝 Quiz: dictionaries in de praktijk
Dit was de laatste gratis les
Je hebt les 1–6 van Een beetje Python gratis gevolgd. Wil je verder? Met één aankoop (€ 24,00, eenmalig) krijg je de overige 33 lessen, de live labs en het certificaat. Geen abonnement.