Abschnitt 8 · Lektion 6
Typhinweise und Docstrings
Eine Funktion, die du vor drei Wochen geschrieben hast, sieht beim Wiederlesen aus wie die von jemand Fremdem. Am Namen erkennst du ungefähr, was sie tut. Was du ihr geben musst und was zurückkommt, steht da nicht.
def zeile(artikel, menge, preis):
return f"{menge} x {artikel}: {menge * preis:.2f} Euro"
print(zeile("Brot", 2, 2.49))
print(zeile(2, "Brot", 2.49)) zeile will einen Artikel, eine Menge und einen Preis. Beim zweiten Aufruf stehen die ersten beiden
in der falschen Reihenfolge, und das endet in der Funktion mit
TypeError: can't multiply sequence by non-int of type 'float'. Übersetzt heißt das, dass sich eine
Folge, hier der Text "Brot", nur mit einer ganzen Zahl malnehmen lässt und nicht mit einer
Kommazahl.
Das stimmt, hilft aber wenig. Die letzte Fundstelle im Traceback ist die Zeile in der Funktion, der Fehler steckt aber im Aufruf, eine Fundstelle darüber. Wenn an der Funktion gestanden hätte, welchen Typ sie wo erwartet, wäre der falsche Aufruf schon beim Schreiben aufgefallen.
Typhinweise
def zeile(artikel: str, menge: int, preis: float) -> str:
return f"{menge} x {artikel}: {menge * preis:.2f} Euro"
print(zeile("Brot", 2, 2.49))
print(zeile("Kaffee", 1, 6.99)) Dafür gibt es Typhinweise (type hints). Hinter jedem Parameter steht nach einem Doppelpunkt der
Typ, den er bekommen soll. artikel: str für Text, menge: int für eine ganze Zahl, preis: float
für eine Kommazahl. Hinter der Klammer zeigt ein Pfeil -> auf den Typ, der zurückkommt, hier
wieder Text. Der Doppelpunkt ganz am Ende der Zeile ist der alte, der den Rumpf einleitet.
Die Namen der Typen kennst du aus Lektion 2.5. Es sind dieselben, die type() ausgibt, also int,
float, str und dazu bool für wahr und falsch. Wenn du jetzt die Zeile mit def liest, weißt
du ohne einen Blick in den Rumpf, was hineingehört. Editoren wissen es auch. Sie zeigen beim Tippen
eines Aufrufs, was als Nächstes erwartet wird, und manche unterstreichen einen Aufruf, der nicht
passt, noch bevor das Programm läuft.
Ein Hinweis, kein Zwang
def verdoppeln(zahl: int) -> int:
return zahl * 2
print(verdoppeln(21))
print(verdoppeln("ab"))
print(verdoppeln(2.5)) Python selbst ist der Hinweis egal. verdoppeln verspricht, eine ganze Zahl zu nehmen und eine
zurückzugeben, und läuft trotzdem mit Text und mit einer Kommazahl. Heraus kommen abab und 5.0,
ohne jede Meldung. Ein Typhinweis ist eben ein Hinweis und kein Türsteher.
Er steht für die Leute, die den Code lesen, und für Werkzeuge, die ihn vorher prüfen. Eins davon heißt mypy. Wer es über ein Programm laufen lässt, bekommt die Aufrufe gemeldet, die nicht zu den Hinweisen passen. Python selbst schaut beim Laufen nicht hin.
Das ist Absicht. Typhinweise kamen erst über zwanzig Jahre nach der Sprache dazu, und Python sollte dadurch nicht strenger werden, sondern lesbarer. Eine Funktion ohne Hinweise ist auch heute noch richtiges Python.
Listen, Dictionaries und None
def namen(gaeste: list[str]) -> str:
return ", ".join(gaeste)
def finde_preis(preise: dict[str, float], artikel: str) -> float | None:
if artikel in preise:
return preise[artikel]
return None
print(namen(["Mia", "Ole", "Aylin"]))
print(finde_preis({"Brot": 2.49, "Milch": 1.19}, "Milch"))
print(finde_preis({"Brot": 2.49, "Milch": 1.19}, "Butter")) Bei Listen und Dictionaries steht in eckigen Klammern dahinter, was darin liegt. list[str] ist
eine Liste aus Texten. dict[str, float] ist ein Dictionary mit Text als Schlüssel und Kommazahlen
als Werten, also genau eine Preisliste.
Neu ist der senkrechte Strich. float | None heißt „eine Kommazahl oder None“. So sieht der
Rückgabetyp jeder Funktion aus, die manchmal nichts findet, wie finde_preis bei der Butter. Gerade
diese Funktionen brauchen den Hinweis am meisten. Wer finde_preis aufruft, muss mit None
rechnen, und am Pfeil sieht man das, bevor ein TypeError es zeigt.
Die Schreibweise mit | gibt es seit Python 3.10. In älterem Code steht dafür Optional[float],
und statt list[str] manchmal List[str] mit großem L. Gemeint ist dasselbe. Nur brauchen diese
beiden oben in der Datei ein from typing import Optional, List.
Ein Docstring
def brutto(netto: float, mwst: int = 19) -> float:
"""Gibt den Preis mit Mehrwertsteuer zurück, auf Cent gerundet."""
return round(netto * (1 + mwst / 100), 2)
help(brutto)
print(brutto(100)) Ein Typhinweis sagt, was hineingeht und herauskommt, aber nicht, was dazwischen passiert. Dafür gibt
es den Docstring (documentation string), einen Text in drei Anführungszeichen als allererste
Zeile im Rumpf. Python legt ihn bei der Funktion ab. help() zeigt ihn an, zusammen mit der Zeile
mit def samt allen Hinweisen.
So sieht die Hilfe zu jeder Funktion aus, auch zu den eingebauten. help(print) zeigt unter anderem
sep und end, auf Englisch.
Das __main__ in der ersten Zeile der Hilfe ist der Name, den Python der gestarteten Datei gibt.
Was es damit auf sich hat, steht in Lektion 8.8.
Ein guter Docstring ist ein Satz und beginnt mit dem, was die Funktion tut, meist mit „Gibt …
zurück“. Ausführlicher wird er dort, wo etwas Unerwartetes passiert. Wann None zurückkommt, in
welcher Einheit ein Wert gemeint ist, was bei einer leeren Liste geschieht. Was ohnehin im Namen und
in den Hinweisen steht, muss er nicht noch einmal sagen.
Ein Kommentar ist kein Docstring
# Gibt den Preis mit Mehrwertsteuer zurück, auf Cent gerundet.
def brutto(netto: float, mwst: int = 19) -> float:
return round(netto * (1 + mwst / 100), 2)
help(brutto)
print(brutto.__doc__) Ein Kommentar über dem def sieht ähnlich aus, ist aber etwas anderes. Wenn help() keinen
Docstring findet, zeigt es zur Not den Kommentar an, samt Raute. Bei der Funktion abgelegt wird er
aber nicht. brutto.__doc__, das Fach, in dem Python den Docstring aufbewahrt, ist None.
Und wenn ein Editor beim Darüberfahren mit der Maus die Beschreibung einer Funktion zeigt, sucht er dafür den Docstring. Der Kommentar bleibt eine Notiz in der Datei. Der Docstring gehört zur Funktion.
Was die Aufgabe will
Drei fertige Funktionen rechnen, was sie sollen, aber an ihnen steht nichts über sie selbst. Jede
bekommt Typhinweise und einen Docstring. Geprüft wird über pruefen.py, und dort wird wie in diesem
Artikel für jede Funktion help() aufgerufen und nachgesehen, ob ein Docstring abgelegt ist.
Zum Mitnehmen
An Typhinweisen wie in def zeile(artikel: str, menge: int) -> str siehst du, was in eine Funktion hineingehört und was herauskommt. Python selbst prüft sie nicht. Der Docstring steht als erste Zeile im Rumpf und erklärt in einem Satz, was die Funktion tut, und help() zeigt beides an.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 6 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.