4.1. Funktionen#
Wir haben im Laufe der vergangenen Stunden bereits einige Funktionen kennengelernt, zum Beispiel die Ausgabe-Funktion print()
, oder die Funktionen enumerate()
und range()
.
4.1.1. Was sind Funktionen?#
Funktionen sind wiederverwendbare Abfolgen von Anweisungen. Sie sind also Codeabschnitte, die mithilfe eines Namens wiederholt ausgeführt werden können. Das Prinzip ist also ein bisschen ähnlich wie Variablen, mit dem Unterschied, dass man mithilfe von Funktionsnamen Anweisungen wiederverwenden kann, während man mithilfe von Variablennamen Objekte wiederverwenden kann. Im Grunde sind Funktionsnamen aber auch zugleich Variablennamen, weil in Python Funktionen auch Objekte sind (-> Grundbegriffe: alles in Python ist ein Objekt).
Jede Funktion besteht aus drei Komponenten:
Name
Parameter (in der Funktionsdefinition: formale Parameter)
Körper
Funktionen werden aufgerufen. Beim Funktionsaufruf werden ihnen Argumente übergeben. Welche Argumente eine Funktion annehmen kann, wird in der Funktionsdefinition mithilfe von formalen Parametern festgelegt. Für Argumente sagt man deswegen auch manchmal tatsächliche Parameter. Funktionen geben immer einen Wert zurück, der heißt dann Rückgabewert. Der Rückgabewert kann in einer Funktionsdefinition mithilfe des Schlüsselworts return
festgelegt werden.
4.1.2. Funktionen definieren#
Funktionen können Nutzer:innen aber auch selbst definieren. Eine Funktionsdefinition hat in Python die allgemeine Form:
Achtung: Zwischen dem Funktionsnamen und der runden Klammer steht KEIN Leerzeichen.
mache_irgendwas ist der Funktionsname.
Die Funktionsparameter heißen parameter_1 und parameter_2. Dabei handelt es sich um Variablennamen, die nur innerhalb der Funktionsdefinition verwendet werden und als Platzhalter für die Argumente dienen, die der Funktion beim Funktionsaufruf übergeben werden. Die formalen Parameter sind also ein bisschen ähnlich wie die Laufvariablen in for-Schleifen, die auch nur Platzhalter für die Elemente aus einem iterierbaren Objekt waren.
Genau wie bei Kontrollstrukturen ist auch bei Funktionen der Funktionskörper alles, was in dem eingeschobenen Block nach dem Doppelpunkt steht, also ein Anweisungsblock und eine return-Anweisung. Im Anweisungsblock stehen irgendwelche Anweisungen, die irgendetwas mit den Parametern der Funktion machen, also im Grunde Verarbeitungsschritte für die Funktionsparameter. Im Laufe der Verarbeitung wird irgendeine Variable definiert, die das “Endergebnis” der Verarbeitung zwischenspeichert. Diese Variable wird dann mithilfe der return-Anweisung zurückgegeben, wenn die Funktion aufgerufen wird. Die return-Anweisung definiert also, welche Variable die Funktion als Rückgabewert beim Funktionsaufruf zurückgeben soll.
Zusätzlich zu diesen unbedingt notwendigen Komponenten können Python-Funktionen einen sogenannten Docstring enthalten. Das ist eine Art Kommentar, der den Nutzer:innen der Funktion erläutert, was die Funktion macht, welche Argumente der Funktion beim Funktionsaufruf übergeben werden können und welcher Wert zurückgegeben wird, nachdem die Anweisungen im Funktionskörper ausgeführt worden sind. Docstrings werden durch drei Anführungszeichen eingegrenzt.
Ein Beispiel:
def is_even(i):
"""
Input: i, a positive int
Returns True if i is even, otherwise False
"""
return i%2 == 0
is_even(3)
False
Quelle: Ana Bell 2016.
Für Funktionsnamen gelten laut Style Guide dieselbe Syntaxempfehlungen wie für Variablennamen: Funktionsnamen sollten in Kleinbuchstaben geschrieben werden und einen Unterstrich sollte verwendet werden, um mehrere Wörter voneinander abzutrennen. Allerdings sollten Funktionsnamen immer irgendeine Operation beschreiben, während Variablennamen in der Regel Substantive sind.
Für Docstrings gibt es daneben einen eigenen Style Guide: https://peps.python.org/pep-0257/.
Bei der Definition der Funktion is_even()
sind zwei Dinge hervorzuheben: Zum einen scheint der Funktionsname nicht so richtig eine Operation auszudrücken, obwohl wir gerade eben besprochen haben, dass Funktionsnamen immer irgendeine Operation beschreiben sollten. Der Name der Funktion is_even()
stellt tatsächlich einen Sonderfall dar: Da diese Funktion einen Wahrheitswert zurückgibt (also entweder True
oder False
), dient die Funktion eigentlich dazu, eine Eigenschaft des Arguments zu überprüfen, das ihr beim Funktionsaufruf übergeben wird, sie liefert also im Grunde eine Antwort auf die Frage, ob das Argument eine gerade oder eine ungerade Zahl ist. Der Funktionsname beschreibt daher die Überprüfung, die vorgenommen wird.
Zum anderen wird der gesamte Ausdruck i%2 == 0
als Rückgabewert definiert. Das erscheint vielleicht im ersten Moment unübersichtlich und vielleicht fallen euch andere Möglichkeiten ein, diese Funktion zu definieren, zum Beispiel:
def is_even(i):
"""
Input: i, a positive int
Prints True if i is even, otherwise False
Returns None
"""
print(i%2 == 0)
return None
is_even(3)
False
Was ist der Unterschied zwischen den beiden is_even()
-Funktionen? Mit der zweiten Funktion kann man nur genau eine Sache machen: Man kann sich das Ergebnis der Operation i%2 == 0
auf dem Bildschirm anzeigen lassen. Der Wahrheitswert wird nur ausgegeben, nicht zurückgegeben. Um das deutlich zu machen, wird hier explizit None
als Rückgabewert festgelegt. Der Rückgabewert könnte hier aber theoretisch auch weggelassen werden, denn wenn bei einer Funktionsdefinition kein Rückgabewert angegeben wird, ist der Rückgabewert automatisch None
. Mit der ersten Funktion kann man dagegen viel mehr machen, denn diese Funktion gibt den Wahrheitswert selbst zurück. Man kann sich den Wert also nicht nur ausgeben lassen, sondern man kann damit auch weiterarbeiten und den Wert zum Beispiel einer Variable zuweisen oder die Funktion zur Formulierung einer bedingten Anweisung verwenden. In den meisten Fällen würden wir deswegen die erste Funktionsdefinition bevorzugen. Aber für den seltenen Fall, dass ihr eine Funktion definiert, die nur etwas ausgeben und nichts zurückgeben soll, solltet ihr wissen, dass solche Funktionen immer None zurückgeben, und dass es besser ist, dies explizit mit return None
anzugeben.
Der PEP8-Styleguide empfiehlt in Bezug auf die Wahl der Funktionsnamen:
“Be consistent in return statements. Either all return statements in a function should return an expression, or none of them should. If any return statement returns an expression, any return statements where no value is returned should explicitly state this as return None, and an explicit return statement should be present at the end of the function […]”
Quelle: PEP8 Styleguide 2016.
Eine Bemerkung zum Schluss: Es wäre keine gute Idee, unsere is_even()
Funktion so umzuschreiben, dass sie erst das Ergebnis der Operation i%2 == 0
mit print(i%2 == 0)
ausgibt und danach den Wert mit return i%2 == 0
zusätzlich zurückgibt: Funktionen sollten immer nur genau eine Aufgabe erfüllen!
Verständnisfrage
Was sind Name, Parameter und Rückgabewert der Funktion
is_even()
?
4.1.3. Funktionen aufrufen#
Funktionsaufrufe haben in Python die allgemeine Form:
Achtung: Auch beim Funktionsaufruf steht zwischen dem Funktionsnamen und der runden Klammer KEIN Leerzeichen.
Wenn eine Funktion aufgerufen wird, dann werden die formalen Parameter aus der Funktionsdefinition durch die Argumente (also die tatsächlichen Parameter) ersetzt. Die Verarbeitungsschritte, die im Funktionskörper für die formalen Parameter definiert sind, werden dann mit den Argumenten ausgeführt.
Bisher haben wir bereits oft Funktionen aufgerufen und ihnen Argumente übergeben. Zum Beispiel:
print("Hallo")
Hallo
lst = [1,3,5,2]
len(lst)
4
Wenn wir die Funktion is_even(), die wir vorhin definiert haben, aufrufen wollen, gehen wir genauso vor:
is_even(5)
False
Manche Funktionen nehmen mehrere Argumente an, die in diesem Fall durch Kommata getrennt aufgelistet werden:
print("Hallo", 4)
Hallo 4
4.1.4. Funktionen verstehen#
In Jupyterlab könnt ihr euch direkt den Docstring zu einer bestimmten Funktion anzeigen lassen. So könnt ihr Informationen, welche Argumente der Funktion beim Funktionsaufruf übergeben werden können, und welchen Wert die Funktion nach dem Ausführen der Anweisungen im Funktionsköroer zurückgibt, abrufen, ohne dafür die Funktion zu googeln.
Dazu bewegt ihr den Cursor auf eine Funktion und gebt dann die Tastenkombination Cmd + I bzw. Ctrl + I ein, oder ihr klickt auf Help -> Contextual Help in der Menüleiste oben.
4.1.5. Wozu werden Funktionen verwendet?#
Allgemein werden Funktionen verwendet…
…um bestimmte Verarbeitungsschritte zu wiederholen, ohne Code ständig kopieren zu müssen.
…um den Code weniger fehleranfällig zu machen: Wenn man den Code kopiert, kopiert man auch mögliche Fehler
Ein Beispiel, wenn es nützlich ist, eine Funktion zu definieren, wäre zum Beispiel die folgende Situation:
Wir wollen die Lieder eine:r Künstler:in analyieren. Hierzu haben wir uns die folgende for-Schleife ausgedacht (eigentlich hat sich allerdings Eric Grimson vom Massachusetts Institute of Technology diese Schleife ausgedacht) :
lyrics = "My mind won't let me rest Voice in my head I hear what it said I can't trust a thing If I picked up and left How fast did you forget? Resting while I'm inside your presence I don't want to think nothing bad This time I won't This time I won't"
lyrics_lst = lyrics.split()
lyrics_dct = {}
for word in lyrics_lst:
if word in lyrics_dct:
lyrics_dct[word] += 1
else:
lyrics_dct[word] = 1
print(lyrics_dct)
{'My': 1, 'mind': 1, "won't": 3, 'let': 1, 'me': 1, 'rest': 1, 'Voice': 1, 'in': 1, 'my': 1, 'head': 1, 'I': 6, 'hear': 1, 'what': 1, 'it': 1, 'said': 1, "can't": 1, 'trust': 1, 'a': 1, 'thing': 1, 'If': 1, 'picked': 1, 'up': 1, 'and': 1, 'left': 1, 'How': 1, 'fast': 1, 'did': 1, 'you': 1, 'forget?': 1, 'Resting': 1, 'while': 1, "I'm": 1, 'inside': 1, 'your': 1, 'presence': 1, "don't": 1, 'want': 1, 'to': 1, 'think': 1, 'nothing': 1, 'bad': 1, 'This': 2, 'time': 2}
Um die Arbeitsschritte mit mehreren Liedern auszuführen, sähe unser Code so aus:
lyrics_gc = "My mind won't let me rest Voice in my head I hear what it said I can't trust a thing If I picked up and left How fast did you forget? Resting while I'm inside your presence I don't want to think nothing bad This time I won't This time I won't"
lyrics_gc_lst = lyrics_gc.split()
lyrics_gc_dct = {}
for word in lyrics_gc_lst:
if word in lyrics_gc_dct:
lyrics_gc_dct[word] += 1
else:
lyrics_gc_dct[word] = 1
print(lyrics_gc_dct)
lyrics_dd = "I'm dreamin', ay Truth be told I got the hardest ahead, yeah But I said I never let it get to my head I be in space, in a daze, while you tellin me things I see your face but I never really heard you say it Red light, green light, either I'ma go New place, corner store Ain't that close anymore Yeah let me get the greens, I'll be home by four If you wanna pour up, then I need me a four"
lyrics_dd_lst = lyrics_dd.split()
lyrics_dd_dct = {}
for word in lyrics_dd_lst:
if word in lyrics_dd_dct:
lyrics_dd_dct[word] += 1
else:
lyrics_dd_dct[word] = 1
print(lyrics_dd_dct)
{'My': 1, 'mind': 1, "won't": 3, 'let': 1, 'me': 1, 'rest': 1, 'Voice': 1, 'in': 1, 'my': 1, 'head': 1, 'I': 6, 'hear': 1, 'what': 1, 'it': 1, 'said': 1, "can't": 1, 'trust': 1, 'a': 1, 'thing': 1, 'If': 1, 'picked': 1, 'up': 1, 'and': 1, 'left': 1, 'How': 1, 'fast': 1, 'did': 1, 'you': 1, 'forget?': 1, 'Resting': 1, 'while': 1, "I'm": 1, 'inside': 1, 'your': 1, 'presence': 1, "don't": 1, 'want': 1, 'to': 1, 'think': 1, 'nothing': 1, 'bad': 1, 'This': 2, 'time': 2}
{"I'm": 1, "dreamin',": 1, 'ay': 1, 'Truth': 1, 'be': 3, 'told': 1, 'I': 7, 'got': 1, 'the': 2, 'hardest': 1, 'ahead,': 1, 'yeah': 1, 'But': 1, 'said': 1, 'never': 2, 'let': 2, 'it': 2, 'get': 2, 'to': 1, 'my': 1, 'head': 1, 'in': 2, 'space,': 1, 'a': 2, 'daze,': 1, 'while': 1, 'you': 3, 'tellin': 1, 'me': 3, 'things': 1, 'see': 1, 'your': 1, 'face': 1, 'but': 1, 'really': 1, 'heard': 1, 'say': 1, 'Red': 1, 'light,': 2, 'green': 1, 'either': 1, "I'ma": 1, 'go': 1, 'New': 1, 'place,': 1, 'corner': 1, 'store': 1, "Ain't": 1, 'that': 1, 'close': 1, 'anymore': 1, 'Yeah': 1, 'greens,': 1, "I'll": 1, 'home': 1, 'by': 1, 'four': 2, 'If': 1, 'wanna': 1, 'pour': 1, 'up,': 1, 'then': 1, 'need': 1}
In der Lösung oben haben wir die for-Schleife einfach kopiert und manuell die Variable lyrics_gc_lst durch die Variable lyrics_dd_lst ersetzt. Das geht bei zwei verschiedenen Liedtexten zwar noch, aber was, wenn wir drei, fünf oder zehn verschiedene Liedtexte haben? Dann produzieren wir extrem viel unnötigen und unübersichtlichen Code, der vielleicht auch noch drei, fünf oder zehnmal denselben Fehler enthält. Eine bessere Lösung ist deswegen hier die Verwendung einer Funktion: die for-Schleife kann damit für alle Liedtexte, die in Python als einfache Zeichenkette repräsentiert werden, verallgemeinert werden:
def lyrics_to_frequencies(lyrics):
"""
Input: lyrics, a string
Return a dictionary with each word in the input string as key and the number of occurrences of the word as value.
"""
lyrics_lst = lyrics.split()
lyrics_dct = {}
for word in lyrics_lst:
if word in lyrics_dct:
lyrics_dct[word] += 1
else:
lyrics_dct[word] = 1
return lyrics_dct
Die Funktion kann dann mit wechselndem Input aufgerufen werden:
lyrics_gc = "My mind won't let me rest Voice in my head I hear what it said I can't trust a thing If I picked up and left How fast did you forget? Resting while I'm inside your presence I don't want to think nothing bad This time I won't This time I won't"
lyrics_dd = "I'm dreamin', ay Truth be told I got the hardest ahead, yeah But I said I never let it get to my head I be in space, in a daze, while you tellin me things I see your face but I never really heard you say it Red light, green light, either I'ma go New place, corner store Ain't that close anymore Yeah let me get the greens, I'll be home by four If you wanna pour up, then I need me a four"
lyrics_gc_freq = lyrics_to_frequencies(lyrics_gc)
lyrics_dd_freq = lyrics_to_frequencies(lyrics_dd)
print(lyrics_gc_freq)
print(lyrics_dd_freq)
{'My': 1, 'mind': 1, "won't": 3, 'let': 1, 'me': 1, 'rest': 1, 'Voice': 1, 'in': 1, 'my': 1, 'head': 1, 'I': 6, 'hear': 1, 'what': 1, 'it': 1, 'said': 1, "can't": 1, 'trust': 1, 'a': 1, 'thing': 1, 'If': 1, 'picked': 1, 'up': 1, 'and': 1, 'left': 1, 'How': 1, 'fast': 1, 'did': 1, 'you': 1, 'forget?': 1, 'Resting': 1, 'while': 1, "I'm": 1, 'inside': 1, 'your': 1, 'presence': 1, "don't": 1, 'want': 1, 'to': 1, 'think': 1, 'nothing': 1, 'bad': 1, 'This': 2, 'time': 2}
{"I'm": 1, "dreamin',": 1, 'ay': 1, 'Truth': 1, 'be': 3, 'told': 1, 'I': 7, 'got': 1, 'the': 2, 'hardest': 1, 'ahead,': 1, 'yeah': 1, 'But': 1, 'said': 1, 'never': 2, 'let': 2, 'it': 2, 'get': 2, 'to': 1, 'my': 1, 'head': 1, 'in': 2, 'space,': 1, 'a': 2, 'daze,': 1, 'while': 1, 'you': 3, 'tellin': 1, 'me': 3, 'things': 1, 'see': 1, 'your': 1, 'face': 1, 'but': 1, 'really': 1, 'heard': 1, 'say': 1, 'Red': 1, 'light,': 2, 'green': 1, 'either': 1, "I'ma": 1, 'go': 1, 'New': 1, 'place,': 1, 'corner': 1, 'store': 1, "Ain't": 1, 'that': 1, 'close': 1, 'anymore': 1, 'Yeah': 1, 'greens,': 1, "I'll": 1, 'home': 1, 'by': 1, 'four': 2, 'If': 1, 'wanna': 1, 'pour': 1, 'up,': 1, 'then': 1, 'need': 1}
4.1.6. Verschachtelte Schleifen durch Funktionen vereinfachen#
Ein weiterer Fall, wenn es Sinn macht, eine Funktion zu definieren, ist, wenn ein Code ineinandergeschachtelte Schleifen enthält. Wenn Schleifen ineinander verschachtelt sind, kann die äußere Schleife nicht unkompliziert abgebrochen werden, sobald die innere Schleife abgebrochen wird.
Das Problem kann auch ohne Funktion gelöst werden: zum Beispiel, indem eine “Break-Out” Variable verwendet wird, um die äußere Schleife abzubrechen:
# Mit Break-Out Variable
einkaufsliste = ["Tomaten", "Kartoffeln", "Äpfel", "Orangen"]
kilopreis = {"Tomaten": 3.99, "Kartoffeln": 2.99, "Möhren": 0.99, "Äpfel": 2.49, "Orangen": 2.99, "Birnen": 2.19}
einkaufsmenge = {"Tomaten": 0, "Kartoffeln": 0, "Äpfel": 0, "Orangen": 0}
gesamtpreis = 0
grenze = 20
grenze_erreicht = False # Break-Out Variable
while True: # unendliche Schleife
for artikel in einkaufsliste:
preis_aktuell = gesamtpreis + kilopreis[artikel]
if preis_aktuell <= grenze:
einkaufsmenge[artikel] += 1
gesamtpreis = preis_aktuell
else:
grenze_erreicht = True
break
if grenze_erreicht:
break
print(einkaufsmenge)
print(gesamtpreis)
{'Tomaten': 2, 'Kartoffeln': 2, 'Äpfel': 1, 'Orangen': 1}
19.440000000000005
Beachtet, dass dieser Beispielschcode fehleranfällig und nicht besonders flexibel ist: Die Laufvariable artikel wird verwendet, um drei verschiedene Objekte zu durchlaufen, nämlich einkaufsliste, kilopreis und einkaufsmenge. Das funktioniert nur deswegen, weil alle drei Objekte dieselben Strings als Elemente enthalten. Jedes Mal wurden die Strings manuell eingegeben. Wenn dabei ein Tippfehler passiert, produziert die Schleife nicht mehr den gewünschten Output.
Eine andere Möglichkeit ist die Verwendung der sogenannten “For-Else Syntax”, welche genau für solche Fälle erfunden wurde: Diese Syntax gibt vor, dass der Anweisungsblock unter Else
nur dann ausgeführt wird, wenn die innere for-Schleife ohne ein break
-Statement terminiert ist.
# Mit For-Else Syntax
einkaufsliste = ["Tomaten", "Kartoffeln", "Äpfel", "Orangen"]
kilopreis = {"Tomaten": 3.99, "Kartoffeln": 2.99, "Möhren": 0.99, "Äpfel": 2.49, "Orangen": 2.99, "Birnen": 2.19}
einkaufsmenge = {"Tomaten": 0, "Kartoffeln": 0, "Äpfel": 0, "Orangen": 0}
gesamtpreis = 0
grenze = 20
while True:
for artikel in einkaufsliste:
preis_aktuell = gesamtpreis + kilopreis[artikel]
if preis_aktuell <= grenze:
einkaufsmenge[artikel] += 1
gesamtpreis = preis_aktuell
else:
break
else: # wird nur ausgeführt, wenn die for-Schleife ohne `break` terminiert ist
continue
break
print(einkaufsmenge)
print(gesamtpreis)
{'Tomaten': 2, 'Kartoffeln': 2, 'Äpfel': 1, 'Orangen': 1}
19.440000000000005
Die Lösungen mit Break-Out Variable und For-Else-Syntax funktionieren zwar, aber sie erfordern zusätzlichen Code und sind etwas unübersichtlich. Eine Lösung, die übersichtlicher ist und keine zusätzlichen Code-Zeilen und Variablen erfordert, sind Funktionen:
# Als Funktion
def einkauf_planen(einkaufsliste, kilopreis, grenze):
"""
Berechnet, wie viele Kilogramm von einem Artikel auf der Einkaufsliste in Abhängigkeit vom aktuellen Kilopreis der Artikel und einem vorgegebenen maximalen Einkaufspreis gekauft werden können, und wieviel der Einkauf kostet.
Parameter:
einkaufsliste, eine Liste mit Elementen vom Typ String, die Einkaufsartikel repräsentieren
kilopreis, ein Dictionary mit den Elementen aus der Einkaufsliste als Schlüssel und dem aktuellen Kilopreis der Artikel als Werte
grenze, ein Integer, der Maximalpreis für den Einkauf in Euro
Gibt ein Tupel mit einem Dictionary einkaufsmenge und einem Float gesamtpreis als Elemente zurück.
"""
einkaufsmenge = dict(zip(einkaufsliste, [0]*len(einkaufsliste))) # Elemente der Einkaufsliste als Schlüssel
gesamtpreis = 0
while True:
for artikel in einkaufsliste:
preis_aktuell = gesamtpreis + kilopreis[artikel]
if preis_aktuell <= grenze:
einkaufsmenge[artikel] += 1
gesamtpreis = preis_aktuell
else:
return (einkaufsmenge, gesamtpreis) # Rückgabewert ist ein Tupel!
einkaufsliste = ["Tomaten", "Kartoffeln", "Äpfel", "Orangen"]
kilopreis = {"Tomaten": 3.99, "Kartoffeln": 2.99, "Möhren": 0.99, "Äpfel": 2.49, "Orangen": 2.99, "Birnen": 2.19}
grenze = 20
einkauf = einkauf_planen(einkaufsliste, kilopreis, grenze)
print(einkauf)
({'Tomaten': 2, 'Kartoffeln': 2, 'Äpfel': 1, 'Orangen': 1}, 19.440000000000005)
Eine Änderung, die durch die Funktionsdefinition notwendig geworden ist, ist, dass die Schlüssel des Dictionaries einkaufsmenge aus den Elementen der Einkaufsliste generiert wurden. Das ist hier notwendig, weil die Funktion sonst nur für exakt dieselbe Einkaufsliste verwendet werden könnte (nämlich eine Einkaufsliste mit den Elementen “Tomaten”, “Kartoffeln”, “Äpfel” und “Orangen”. Diese Änderung macht unsere Schleife zugleich deutlich flexibler, denn jetzt müssen nicht dreimal dieselben Strings eingegeben werden, sondern nur zweimal.
Beachtet auch, dass die Funktion hier ein Tupel zurückgibt, das aus den beiden Objekten einkaufsmenge und gesamtpreis zusammengesetzt ist. Auf die einzelnen Elemente kann man ganz regulär mit dem Zugriffsoperator [] für sequentielle Datentypen zugreifen:
einkaufsmenge = einkauf[0]
print(einkaufsmenge)
gesamtpreis = einkauf[1]
print(gesamtpreis)
{'Tomaten': 2, 'Kartoffeln': 2, 'Äpfel': 1, 'Orangen': 1}
19.440000000000005
Note
Tupel können also verwendet werden, wenn eine Funktion mehrere Werte zurückgeben soll.
Wir haben mit der Funktionsdefinition also unseren Code bereits in zwei Aspekten verbessert: Es ist weniger Code notwendig, um auch die äußere Schleife abzubrechen, und wir haben die Variable einkaufsmenge in Abhängigkeit von der Variable einkaufsliste definiert, sodass Änderungen an der Variable einkaufsliste auch direkt auf die Variable einkaufsmenge übertragen werden, ohne, dass wir den Code kopieren müssen.
Verständnisfragen
Wie könnte man die Definition der Funktion noch weiter verbessern? Betrachtet dazu verschiedene mögliche Situationen:
Was wird zurückgegeben, wenn
einkaufsliste
eine leere Liste ist?Was passiert, wenn der Wert der Variable grenze kein Integer, sondern ein Float ist (zum Beispiel bei 10.5)?
Was passiert beim Ausführen der Zugriffsoperation
kilopreis[artikel]
, wenn der entsprechende Artikel nicht im Dictionarykilopreis
enthalten ist?Die Schleife terminiert, sobald durch Hinzufügen des aktuellen Artikel in der Einkaufsliste die Grenze überschritten wird. Wie könnte der Code so umgeschrieben werden, dass zunächst überprüft wird, ob es irgendeinen Artikel in der Einkaufsliste gibt, der hinzugefügt werden könnte, ohne, dass die Grenze überschritten wird?
Könnte man das Objekt
einkaufsmenge
auch anders erstellen, zum Beispiel durch Verwenden der Dictionary-Methode.setdefault()
?
4.1.7. Sichtbarkeitsbereich von Variablen (Variable Scope)#
Wenn wir Variablen erstellt haben, sind wir immer davon ausgegangen, dass diese im gesammten Programm mithilfe des Namens abrufbar sind. Wenn wir selbst eine Funktion definieren, und dabei eine Variable vorkommt, dann ist die im restlichen Programm jedoch im Normalfall nicht abrufbar, sie “existiert” sozusagen nur in der Funktion und ist im restlichen Programm nicht sichtbar. Die Sichtbarkeit von Variablen im eigenen Code lässt sich leicht mithilfe von pythontutor.com nachvollziehen.
Kopiert als Beispiel den Code der Funktion einkauf_planen in PythonTutor und führt ihn dort aus.
Es ist bei der Definition von Funktionen wichtig zu beachten, dass innerhalb der Funktionsdefinition keine Variablen aus dem Bereich außerhalb der Funktion verwendet werden sollten. Beispielsweise dürfte nicht einfach der Parameter grenze aus der Funktionsdefinition entfernt werden, denn sonst wird beim Aufruf der Funktion eine gleichnamige Variable aus dem Bereich außerhalb der Funktion eingesetzt. Dadurch hat die Funktion ein für Nachnutzer:innen unvorhersehbares Verhalten! Alle Objekte, welche zum Ausführen der Funktion benötigt werden, sollten deswegen entweder innerhalb der Funktion erstellt werden oder beim Aufruf als Argumente übergeben werden.
4.1.8. Was sind Methoden?#
Wir haben Methoden bisher definiert als die Operationen, die speziell für Objekte eines bestimmten Datentyps definiert sind. Jetzt können wir diese Definition schärfen: Methoden haben genau wie Funktionen einen Namen, Parameter und einen Funktionskörper, und sie geben einen Wert zurück. Aber im Unterschied zu Funktionen sind sie nur für Objekte eines bestimmten Datentyps definiert.
Wir haben bereits den Punkt-Operator .
kennengelernt, mit dem Methoden aufgerufen werden können:
wort = "Hallo"
wort.lower()
'hallo'
Note
In diesem Kapitel wurde häufig der Begriff “Fehler” verwendet. Während sich umgangssprachlich jede:r etwas darunter vorstellen kann, gibt es für diesen wie für viele andere Begriffe in der professionellen Softwareentwicklung feststehende, wenn auch nicht immer einheitliche Definitionen, für die es zum Teil sogar internationale Normen gibt. Die ISO Norm 24765 unterscheidet beispielsweise zwischen error, fault, defect und failure (ISO24765:2017). Der Unterschied wird durch die deutschsprachige Übersetzung durch das International Software Testing Qualifications Board intuitiv deutlich: Demnach ist ein error eine Fehlhandlung einer Person, welche einen Fehlerzustand, einen Defekt in einem Programm verursachen kann (defect oder fault). Dieser Fehlerzustand kann eine Fehlerwirkung, das Versagen des Programms, herbeiführen (failure) (laut Glossar des ISTQB). Nach dieser Definition ist das, was wir in diesem Kapitel mit Fehler meinen eigentlich der Defekt im Code, der beim Ausführen zu einem Versagen führen kann.
4.1.9. Quellen#
ISO/IEC/IEEE 24765:2017(en). Systems and Software Engineering — Vocabulary. 2017. URL: https://www.iso.org/obp/ui/en/#iso:std:iso-iec-ieee:24765:ed-2:v1:en.
Ana Bell. Decomposition, Abstraction, and Functions. 2016. URL: https://ocw.mit.edu/courses/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/resources/lecture-4-decomposition-abstraction-and-functions/.
International Software Testing Qualifications Board. Standardglossar der Fachbegriffe im Softwaretesten. URL: https://glossary.istqb.org/de_DE/home.
David Goodger and Guido van Rossum. PEP 257 - Docstring Conventions. 2022. URL: https://peps.python.org/pep-0257/.
Eric Grimson. Recursion and Dictionaries. 2016. URL: https://ocw.mit.edu/courses/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/resources/lecture-6-recursion-and-dictionaries/.
Stefan Hartinger. Programmieren in Python. Kapitel 6: Funktionen. 2021. URL: https://www.uni-regensburg.de/assets/physik/fakultaet/IT/Tutorials-Installation-Programming-Environment/Programmieren_in_Python.pdf.
Leodanis Pozo Ramos. The Python return Statement. Usage and Best Practices. URL: https://realpython.com/python-return-statement/.
Python 3.11.3 Documentation. Defining Functions. URL: https://docs.python.org/3/tutorial/controlflow.html#defining-functions.
Python 3.11.3 Documentation. Glossary: Functions. URL: https://docs.python.org/3/glossary.html#term-function.