Programmieren mit PythonFunktionen
Eine gute Faustregel ist, dass eine Funktion ausreichend allgemein sein sollte, um wiederverwendbar zu sein, ohne schon vorhandene Logik zu duplizieren, aber auch so speziell, dass man sie wirklich einsetzen kann.
Übung
Wie könnte das Design des folgenden Codes verbessert werden?
def loesche_ein_anfangs_leerzeichen(S):
if S[0] == " ":
return S[1:]
else:
return S
def loesche_zwei_anfangs_leerzeichen(S):
if S[0:2] == " ":
return S[2:]
else:
return S
def loesche_drei_anfangs_leerzeichen(S):
if S[0:3] == " ":
return S[3:]
else:
return S
Lösung. Wir sollten eine einzige Funktion haben, um die Anzahl der überflüssigen Leerzeichen am Anfang eines Strings zu entfernen. Das obige Design hat das Problem, dass wir herausfinden müssen, wie viele führende Leerzeichen es gibt, bevor wir die entsprechende Funktion aufrufen können, was bedeutet, dass die meiste Arbeit, die eigentlich von der Funktion ausgeführt werden sollte, schon vor dem Aufruf der Funktion ausgeführt werden muss. Dadurch wird die Auslagerung der Arbeit nicht umgesetzt.
Argumente
Die Objekte, die einer Funktion beim Aufruf übergeben werden, werden als Argumente der Funktion bezeichnet. Die Variablen, die die Argumente in der Funktionsdefinition darstellen, werden Parameter genannt. Der eingerückte Codeblock, der beim Aufruf der Funktion ausgeführt wird, ist der Body der Funktion (Funktionsrumpf).
Übung
Im folgenden Codeblock ist s
ein "hello"
ein
def dupliziere(s):
return s + s
dupliziere("hello")
Wir können Parametern Standardwerte zuweisen und für diese Parameter die Argumente dann optional (nicht zwingend) beim Aufruf der Funktion angeben.
def gerade(k, x, d=0): return k * x + d gerade(2,3) # ergibt 6 gerade(5,4,d=2) # ergibt 22
Die Argumente 2, 3, 4 und 5 in diesem Beispiel werden Positionsargumente genannt, und b=2
ist ein Schlüsselwort-Argument.
Wenn der Funktionsteil mit einem String-Literal beginnt, wird dieser String als Dokumentation für die Funktion interpretiert. Dieser DocString hilft dir und anderen Benutzern deiner Funktionen, schnell festzustellen, wie sie verwendet werden sollen. Auf die Dokumentation einer Funktion kann in einer Python-Sitzung über die Funktion help
zugegriffen werden. Beispielsweise gibt help(print)
den DocString für die eingebaute print
-Funktion aus.
Anonyme Funktionen
Eine Funktion kann definiert werden, ohne ihr einen Namen zu geben. Eine solche Funktion gilt als anonym. Die Python-lambda
. Eine häufige Situation, in der anonyme Funktionen nützlich sein können, ist die Bereitstellung einer Funktion an eine andere als Argument. Zum Beispiel:
def mache_drei_mal(f, x): return f(f(f(x))) mache_drei_mal(lambda x: x*x, 2)
Eine Funktion mit mehreren Argumenten funktioniert ähnlich, wobei die Parameter durch Kommas getrennt sind: Der Additionsoperator + könnte folgendermaßen geschrieben werden lambda x,y: x + y
.
Übung
Schreibe eine Funktion, die zwei Argumente a
und b
und eine Funktion f
übernimmt und a
zurückgibt, wenn f(a) < f(b)
und b
ansonsten. Verwende dann die anonyme Funktionssyntax, um deine Funktion mit zwei Zahlen und der Negationsfunktion aufzurufen.
Lösung. Eine mögliche Lösung:
def which_smaller(a, b, f):
if f(a) < f(b):
return a
else:
return b
which_smaller(4, 6, lambda x: -x)
Gültigkeitsbereich
Der Gültigkeitsbereich einer Variablen ist der Bereich im Programm, in man sie aufrufen kann. Wenn du beispielsweise x
in Zeile 413 deiner Datei so definierst, dass es auf 47
gesetzt wird und einen Fehler erhältst, weil du versucht hast, x
in Zeile 35 zu verwenden, dann besteht das Problem darin, dass die Variable hier noch nicht gültig ist.
Eine Variable, die im Hauptteil (main body) einer Datei definiert ist, hat globalen Gültigkeitsbereich, was bedeutet, dass sie von ihrem Definitionspunkt aus im gesamten Programm sichtbar ist.
Eine im Funktionsrumpf definierte Variable befindet sich im lokalen Gültigkeitsbereich dieser Funktion. Zum Beispiel:
def f(x): y = 2 return x + y y
Übung
Versuche, eine Funktionsdefinition in eine andere zu verschachteln. Sind Variablen im äußeren Funktionsrumpf in der inneren Funktion verfügbar? Wie ist das im umgekehrten Fall?
def f(): def g(): j = 2 return i print(j) i = 1 return g() f()
Lösung. Die in der inneren Funktion definierte Variable ist nicht im Gültigkeitsbereich des Funktionsrumpfs der äußeren Funktion, aber die im Funktionsrumpf der äußeren Funktion definierte Variable ist im Gültigkeitsbereich der inneren Funktion.
Testen
Es wird dringend empfohlen, Tests zu deinen Funktionen zu schreiben und zur Verfügung zu stellen. Damit wird bestätigt, dass sich jede Funktion wie erwartet verhält. Dies ist besonders wichtig, wenn deine Codebasis immer größer wird, da Änderungen in einer Funktion zu Problemen in anderen Funktionen führen können, die sie verwenden. Eine Möglichkeit zum Testen der Funktionen in deiner gesamten Codebasis hilft dir dabei, diese Fehler schnell zu entdecken, bevor sie zu Schäden führen.
Eine gängige Methode dazu (die du in diesem Kurs bereits mehrfach gesehen hast) ist das Schreiben von Funktionen, deren Namen mit test_
beginnen und die assert
-Anweisungen enthalten. Eine assert
-Anweisung löst einen Fehler aus, wenn der darauf folgende Ausdruck False
zurück gibt. Man kann die Testfunktionen direkt ausführen, oder man kann ein Tool wie pytest verwenden, um alle Testfunktionen in der Codebasis zu finden und auszuführen.
def space_concat(s,t): """ Verkettung der Strings s und t, wobei ein Leerzeichen zwischen ihnen eingefügt wird, wenn s mit einem Nicht-Leerzeichen endet und t mit einem Nicht-Leerzeichen beginnt """ if s[-1] == " " or t[0] == " ": return s + t else: return s + " " + t def test_space_concat(): assert space_concat("foo", "bar") == "foo bar" assert space_concat("foo ", "bar") == "foo bar" test_space_concat() space_concat("foo", "bar")
Übung
Die obigen Testfälle decken nicht die ungünstige Situation ab, dass einer der Strings leer ist. Liefert die Funktion korrekte Werte für diese ungünstigen Fälle?
Lösung. Wir überprüfen ob die Strings leer sind bevor wir die letzten bzw. ersten Zeichen überprüfen. Mit or
kann das Problem schnell gelöst werden, weil wenn der erste bool-Wert in einer or
Operation True
ist, wird der zweite erst gar nicht ausgewertet.
def space_concat(s,t): """ Verkettung der Strings s und t, wobei ein Leerzeichen zwischen ihnen eingefügt wird, wenn s mit einem Nicht-Leerzeichen endet und t mit einem Nicht-Leerzeichen beginnt. """ if s == "" or t == "" or s[-1] == " " or t[0] == " ": return s + t else: return s + " " + t def test_space_concat(): assert space_concat("foo", "bar") == "foo bar" assert space_concat("foo ", "bar") == "foo bar" assert space_concat("foo", "") == "foo" assert space_concat("", "bar") == "bar"
Übungen
Übung
Schreibe eine Funktion, die zwei Strings als Eingabe akzeptiert und die Verkettung dieser beiden Strings in alphabetischer Reihenfolge zurückgibt.
Tipp: Was meinst du, mit welchem Operator Strings alphabetisch verglichen werden können.
def alphabetical_concat(s,t): pass # hier Code einfügen def test_concat(): assert alphabetical_concat("buchstaben", "suppe") == "buchstabensuppe" assert alphabetical_concat("socken", "rote") == "rotesocken" return "Tests erfolgreich bestanden!" test_concat()
Lösung.
def alphabetical_concat(s,t): if s < t: return s + t else: return t + s def test_concat(): alphabetical_concat("buchstaben", "suppe") == "buchstabensuppe" alphabetical_concat("food", "brain") == "brainfood" return "Tests passed!" test_concat()