Level 05: Dictionaries

Level 5: Dictonaries #

Nun kennst Du mit Liste und Tupel zwei Datenstrukturen, auf die du mit Angabe eines Index’ zugreifen kannst. (Also z.B. auf das zweite(!) Element mit meineListe[1].) Wäre es nicht manchmal praktisch, sich nicht merken zu müssen, wo ein Element gespeichert ist, sondern gleich eine Art Beschriftung zu haben?

Eine einfache Möglichkeit, so etwas zu tun, bietet eine andere Datenstruktur: das Dictionary. Versuche es selbst! Lege einmal in thonny eine Datei mit folgendem Inhalt an:

telefon = {'Ada': '(0)20 1012 1815',
           'Douglas': '(0)20 6742 4242',
           'Alan': '(0)20 3862 3598'}

Nun kannst Du das Programm laufen lassen und dir in der REPL Telefonnummern anzeigen lassen:

>>> telefon['Ada']
'(0)20 1012 1815'

Was passiert hier? #

Wenn Du »Dictionary« mit »Lexikon« übersetzt, kommst du der Sache schon nahe. In einem Lexikon findest du zu jedem Stichwort einen Eintrag. So ist es auch hier: im Dictionary werden sogenannte Schlüssel-Wert-Paare gespeichert. Ein Eintrag (value, für Wert) wird einem bestimmten Stichwort (key, für Schlüssel) zugeordnet. Nach den Schlüsseln lässt sich nun sehr unkompliziert suchen.

Die Suche und Zuordnung in einem Dictionary ist sehr schnell – dies wird durch ein Verfahren erreicht, bei dem ein sogenannter Hash für jeden Schlüssel erzeugt wird, und darüber die Stelle bestimmt wird, an der der zugehörige Wert gespeichert wird.

Nützliche Metoden von Dictionaries #

telefon = {'Ada': '(0)20 1012 1815',
           'Douglas': '(0)20 6742 4242',
           'Alan': '(0)20 3862 3598'}

Nimm Dir noch einmal das Beispiel von vorhin vor, und probiere einige nützliche Methoden aus. “Wenn du `help(dict())’ im REPL eingibst, bekommst du alle verfügbaren Methoden angezeigt. (Hier behandele ich nur eine kleine Auswahl.)

get #

Neben dem direkten Zugriff mit (z.B.) telefon['Ada'] gibt es noch eine andere Methode, die eine nützliche Eigenschaft hat. In der Hilfe findest du zu get folgenden Eintrag:

get(self, key, default=None, /)
    Return the value for key if key is in the dictionary, else default.

get nimmt also den Schlüssel entgegen und gibt den Wert zurück, oder – falls der Schlüssel nicht vorhanden ist – standardmäßig (default=None) nichts. Warum ist das überhaupt nötig?

>>> telefon['Arnold']
Traceback (most recent call last):
  File "/usr/lib64/python3.10/idlelib/run.py", line 580, in runcode
    exec(code, self.locals)
  File "<pyshell#2>", line 1, in <module>
KeyError: 'Arnold'
>>> telefon.get('Arnold')
>>>

Aha! Während der direkte Zugriff eine Fehlermeldung wirft (und so vielleicht unser Programm zum Abstürzen bringen könnte), gibt get einfach nichts zurück, oder etwas, das du selbst bestimmten kannst (z.B. mit telefon.get(‘Arnold’, ‘Nummer nicht bekannt’). Das kannst Du im Programm berücksichtigen, und dein Programm wird stabiler laufen!

items #

Für die Methode items findest du mit help(dict()) folgenden Eintrag:

items(...)
    D.items() -> a set-like object providing a view on D's items

Mit items sind hier die im Dictionary enthaltenen Schlüssel-Wert-Paare gemeint, und wir bekommen sie »set-like«, also ähnlich einer Menge (so etwas kennst du doch schon!) zurück. Wie lassen sich Schlüssel-Wert-Paare gut weiterreichen? Natürlich! Als Tupel! Und auf einer Menge funktioniert doch auch eine for-Wiederholung…

Erweitere doch das Beispiel um einige Zeilen. Was passiert, wenn du das Programm jetzt laufen lässt?

telefon = {'Ada': '(0)20 1012 1815',
           'Douglas': '(0)20 6742 4242',
           'Alan': '(0)20 3862 3598'}

for eintrag in telefon.items():
    vorname, tel_num = eintrag
    print(vorname + ' hat die Nummer ' + tel_num)

Wenn alles geklappt hat, sollten jetzt für jeden im Dictionary enthaltenen Namen eine gut lesbar Zeile mit Name und Telefonnummer ausgegeben werden. Einen solchen Zugriff auf ein Dictionary (oder auch eine Liste) mit einer for-Wiederholung nennt man übrigens auch »über ein Dictionary (oder eine Liste) iterieren«.

popitem #

In der Hilfe zum Dictionary (help(dict())) findet sich auch folgender interessanter Eintrag:

popitem(self, /)
    Remove and return a (key, value) pair as a 2-tuple.
    
    Pairs are returned in LIFO (last-in, first-out) order.
    Raises KeyError if the dict is empty.

Dass items als Tupel zurückgegeben werden, damit können wir schon umgehen. Gleichzeitig wird der Eintrag, der zurückgegeben aus dem Dictionary entfernt.

Interessant ist die Information danach: LIFO erscheint erst als Einschränkung, kann aber sehr nützlich zu sein. Das, was als letztes hinzugefügt wurde, wird als erstes zurückgegeben. Du kannst dir diese Prinzip wie einen Stapel Papier vorstellen: das Blatt, das du zuletzt oben auf den Stapel legst, ist auch das, was du als erstes wieder herunternimmst.

Hier als Beispiel ein kleines (und zugegeben: etwas umständliches Programm), das das Prinzip zeigen soll. Lies dir den Code durch – was wird wohl passieren? Übernimm dann das Programm in Thonny und lass es laufen!

satz = 'Ich bin ein Satz und werde nun umgedreht.'
zeichen_nr = 1
stapel_dict = dict()

for zeichen in satz:
    stapel_dict[zeichen_nr] = zeichen
    zeichen_nr += 1

satz_rückwärts = ''
while stapel_dict:
    wegwerfnummer, buchstabe_von_hinten = stapel_dict.popitem()
    satz_rückwärts += buchstabe_von_hinten

print(satz_rückwärts)

Wenn du eine Reihe Blätter auf einen Stapel legst, sie dann der Reihen nach herunternimmst und wieder nebeneinander legst, wird sich die Reihenfolge umgekehrt haben – genau das ist hier passiert.

Truthiness

Hinweis zur Einordnung

Vielleicht hast du dich über die Zeile while stapel_dict gewundert? Das ist ein kleiner Trick, der als sehr pythonisch angesehen wird. (In anderen Programmiersprachen ist das oft komplizierter…) So lange noch Elemente im stapel_dict enthalten sind, wird das Dictionary an dieser Stelle zu True ausgewertet. Wenn es hingegen leer wird (weil ja mit popitem() nach und nach alle Elemente entfernt werden) wird es zu False ausgewertet, und die while-Wiederholung wird verlassen. (Übrigens funktioniert das mit vielen Datentypen: wenn du einmal bool(0), bool(42), bool('Hallo') usw. in den REPL schreibst, kannst du das ausprobieren.)

Übung #

Sieh dir die for-Wiederholung unten an. Überlege, wie die Daten ins friends-Dictionary eingefügt werden müssen, damit das Vorgehen funtkioniert. Füge einige Freunde mit ihren Geburtsdaten hinzu und sieh dir an, was passiert!

from datetime import datetime

friends = {}

# Füge an dieser Stelle einige Freund_innen mit ihren Geburtsdaten ins friends-Dictionary ein!

for eintrag in friends.items():
    freund_in, geburtstag = eintrag
    tag, monat, jahr = geburtstag
    alter = datetime.now() - datetime(jahr, monat, tag)
    print(freund_in + ' ist jetzt ' + str(alter.days) + ' Tage alt.')
Lösungsvorschlag
from datetime import datetime

friends = {}

# Füge an dieser Stelle einige Freund_innen mit ihren Geburtsdaten ins friends-Dictionary ein!
friends['Ada'] = (10, 12, 1815)
friends['Alan'] = (23, 6, 1912)
friends['Douglas'] = (11, 2, 1952)

for eintrag in friends.items():
    freund_in, geburtstag = eintrag
    tag, monat, jahr = geburtstag
    alter = datetime.now() - datetime(jahr, monat, tag)
    print(freund_in + ' ist jetzt ' + str(alter.days) + ' Tage alt.')

Zurück Weiter