5. Funktionen¶
Funktionen sind Unterprogramme und helfen die
- Les- und Wartbarkeit bei großen Programmen zu erleichtern (durch Unterteilung in Unterprogramme) sowie
- Redundanzen zu vermeiden (wiederkehrende Algorithmen nur 1x implementiert).
Diese
- benötigen i.A. Eingaben - sog. Parameter (Argumente) - und
- liefern (damit berechnete) Ergebnisse zurück, genannt Rückgabewerte.
Wir haben bereits Funktionen kennengelernt, z.B. len
In [1]: neue_liste = [3, 5, 9]
In [2]: len(neue_liste)
Out[2]: 3
neue_liste
ist dabei ein Parameter, 3
der Rückgabewert.
Den Rückgabewert kann man in eine Variable speichern und weiter verarbeiten mittels:
In [3]: a = len(neue_liste)
In [4]: a
Out[4]: 3
In [5]: print("Laenge=", a)
Laenge= 3
Bei den Sequenzen haben wir auch schon Funktionen von Klassen in Aktion gesehen:
In [7]: s = "Zeichenkette" # erzeuge Zeichenkettenobjekt s
In [8]: s.count('e') # zaehle Buchstaben 'e'
Out[8]: 4
Diese werden mit Objektnamen (s
) plus Punkt (.
) plus Funktionsname (count()
) aufgerufen.
Genaueres dazu später.
5.1. Erste Schritte¶
Eine Funktion startet in Python mit dem Schlüsselwort def
.
Syntaktisch sieht die Definition folgendermaßen aus:
def Funktionsname( Parameterliste ) :
Anweisung
...
Anweisung
return Ergebnis # Optional
Beispiel:
def fak(zahl):
ergebnis = 1
for i in range(2, zahl+1):
ergebnis *= i
return ergebnis
Dieser Funktion wird die Variable zahl
als Parameter im Anweisungskopf übergeben.
Der Anweisungskörper berechnet die Fakultät dieser Zahl und gibt das Ergebnis zurück.
Speichert man diesen Quellcode in ein .py
File und führt dieses aus,
passiert nichts! Warum?
- Die Funktion wurde zwar eingelesen, aber
- kein Befehl ruft diese Funktion auf!
Dieses Skript muss entsprechend erweitert werden, damit
es die Funktion aufruft (funktion_fak.py
):
# Funktion
def fak(zahl):
ergebnis = 1
for i in range(2, zahl+1):
ergebnis *= i
return ergebnis
# Hauptprogramm
while True:
eingabe = int(input("Gib eine Zahl ein: "))
print(fak(eingabe))
Dieses File (z.B. funktion_fak.py
) kann man nun:
- In Spyder laden und ausführen oder
- in einer Python Shell mit
python funktion_fak.py
starten oder - in der IPython-Konsole ausführen mit
runfile("funktion_fak.py")
(Voraussetzung man befindet sich in diesem Verzeichnis)
Bemerkung
- Die
while
Anweisung ist eine Endlosschleife, nicht gut, fürs Beispiel ok. - Die Kommunikation funktioniert über Parameter und
return
Anweisung. - Man erkennt: Aufteilung, keine Redundanzen, Hauptprogramm und Funktion
- Die IPython-Konsole ist eine Linux Shell. siehe https://jakevdp.github.io/PythonDataScienceHandbook/01.05-ipython-and-shell-commands.html Z.B.
ls
(list) zeigt den Verzeichnisinhalt an, mitcd
wechseln man Verzeichnisses)
5.2. Funktionsparameter¶
5.2.1. Optionale und Schlüsselwortparameter¶
Parameter können
- obligatorisch sowie
- optional sein (mit
=
in Parameterliste).
Die optionalen Parameter (0 oder mehr) folgen den obligatorischen.
Zum Verständnis ein Beispiel (funktion_add.py
):
def add(x, y=5):
"""Gib x plus y zurueck."""
return x + y
Aufrufe, hier einmal interaktiv:
In [1]: runfile("funktion_add.py")
In [2]: add(4)
Out[2]: 9
In [3]: add(8,3)
Out[3]: 11
Bemerkung
runfile
führt das file aus, dadurch wird die Funktion definiert und kann verwendet werden.x
ist obligatorisch.y
ist optional, wird diese Zahl nicht angegeben isty=5
(Default Wert).
Es gibt unterschiedliche Möglichkeiten des Aufrufes. Zunächst eine Funktionsdefinition:
Beispiel (funktion_sumsub.py
):
def sumsub(a, b, c=0, d=0):
return a - b + c - d
Mögliche Aufrufe:
In [1]: runfile("funktion_sumsub.py")
In [2]: sumsub(12,4) #1 nur a und b übergeben, Rest = Defaultwerte
Out[2]: 8
In [3]: sumsub(12,4,27,23) #2 Position optionaler Argumente fix
Out[3]: 12
In [4]: sumsub(12,4,d=23,c=27) #3 Position optionaler Argumente beliebig!
Out[4]: 12
Da die Position aller Argumente beim 1. und 2. Aufruf fix ist, nennt man diese positional arguments.
Beim 3. Aufruf ist die Position optionaler Argumente beliebig. Die Zuordnung funktioniert
über einen Schlüssel (d=
, c=
). Daher nennt man diese auch Schlüsselwortparameter (keyword arguments)
Hinweis
Der Aufruf #2
ist zu vermeiden da fehleranfällig. Besser ist #3
aber mit gleicher Reihenfolge wie
bei der Funktionsdefinition c=27,d=23
!
5.2.2. call-by-value / reference¶
Je nach Programmiersprache gibt es unterschiedliche Möglichkeiten wie Argumente übergeben werden:
call-by-value: Hier wird funktionsintern mit Kopien der als Parameter übergebenen Instanzen gearbeitet.
Vor- Nachteile: Eine Funktion kann keine Änderungen von Instanzen aus dem Hauptprogramm bewirken, erzeugt jedoch unter Umständen einen erheblichen Overhead.
call-by-reference: Dabei wird funktionsintern mit Referenzen (Pointer) auf die im Hauptprogramm befindlichen Instanzen gearbeitet.
Vor- Nachteile: „Schlanke“ Calls da keine Daten erzeugt werden, jedoch besteht die Gefahr, dass eine Funktion Instanzen aus dem Hauptprogramm ändern kann.
Python verwendet diesbezüglich eine Mischform.
Eine call-by-reference gibt es nur bei veränderbaren Datentypen (list
, dict
).
Beispiel:
In [1]: def test(liste):
...: liste += [5,6,7]
...:
In [2]: zahlen = [1,2,3]
In [3]: print(zahlen)
[1, 2, 3]
In [4]: test(zahlen)
In [5]: print(zahlen)
[1, 2, 3, 5, 6, 7]
Man sieht die (ungewollte) Veränderung der Liste im Hauptprogramm. Vermeidung einfach durch kopieren beim Funktionsaufruf, d.h.:
In [6]: zahlen = [1,2,3]
In [7]: test(zahlen[:])
In [8]: print(zahlen)
[1, 2, 3]
Bei unveränderbaren Datentypen (z.B. Zahlen) wird ein call-by-value verwendet.
Beispiel:
In [1]: def test_zahl(zahl):
...: zahl += 2
...:
In [2]: a = 2
In [3]: print(a)
2
In [4]: test_zahl(a)
In [5]: print(a)
2
Der Grund dafür, man hat in Python versucht die obigen Vor- und Nachteile ideal zu kombinieren.
5.2.3. Docstring¶
Möchte man eine Funktion dokumentieren, schreibt man direkt nach dem Anweisungskopf einen Kommentar welcher mit """
(dreifaches Anführungszeichen)
startet und endet.
Den Text dazwischen nennt man docstring.
Beispiel Funktion (funktion_add.py
):
def add(x, y=5):
"""Gib x plus y zurueck."""
return x + y
Den docstring kann man folgendermaßen ausgeben:
In [1]: runfile("funktion_add.py")
In [2]: add.__doc__
Out[2]: 'Gib x plus y zurueck.'
Die magische Funktion __doc__
retourniert den docstring.
5.3. Namensräume¶
Innerhalb eines Programmes gibt es einen Satz von Variablen. Diese sieht man in Spyder direkt.
Diesen Variablen sind sogenannten Namensräumen zugewiesen in dem diese Werte definiert wurden.
Es gibt eigene Namensräume (namespaces) z.B. innerhalb:
Die Funktionen globals()
und locals()
zeigen den Inhalt dieser Namensräume.
Beispiel: Zunächst definieren wir eine Funktion im File (funktion_namespaces.py
):
def check():
x = 123
print(locals())
print(globals())
und starten den interaktiven Modus:
In [1]: globals()
Out[1]:
{'__name__': '__main__',
...
'_i1': 'globals()'}
In [2]: runfile("funktion_namespaces.py")
In [3]: globals()
Out[3]:
{'__name__': '__main__',
...
'check': <function __main__.check()>,
'_i3': 'globals()'}
Man sieht folgendes :
globals()
gibt eindict
zurück. Es gibt schon vordefinierte Wertepaare.- Mit
runfile
wurde ausfunktion_namespaces.py
die Funkioncheck
geladen (sowie viele andere Dinge).
Dadurch kann diese Funktion erst aufgerufen werden mit:
In [4]: check()
{'x': 123}
{'__name__': '__main__',
...
'check': <function check at 0x000002A7AF5999D8>}
Man sieht folgendes :
- Der Funktionskörper kennt (auch)
globals()
, diese sind unverändert (d.h.check()
kann sich selbst aufrufen)- Weiters kennt der Funktionskörper
x
(lokale Variable),- das Hauptprogramm kennt
x
aber nicht (keine globale Variable)!...
bedeutet es gibt wesentlich mehr Ausgabe in der Shell als hier dargestellt.
Hier ein Beispiel
In [1]: def test():
...: x = 1 # lokale Variable
...: print(x)
...:
In [2]: x=2 # globale Variable
In [3]: test() # Ausgabe der lokalen Variable x
1
In [4]: x # Ausgabe der globalen Variable x
Out[4]: 2
Abhilfe schafft eine global
Anweisung.
In [1]: def test():
...: global x # x ist auch im globalen namespace sichtbar
...: x = 1
...: print(x)
...:
In [2]: x = 2
In [3]: test() # ueberschreibt x auch global
1
In [4]: x
Out[4]: 1
Warnung
Diese Art der Programmierung ist nicht gut, weil unübersichtlich und fehleranfällig! Ein typisches Beispiel sind globale Zähler z.B. bei Lizenzabfragen.
Besser ist in diesem Fall ein return
Statement:
In [1]: def test():
...: x = 1
...: print(x)
...: return x
...:
In [2]: x = 2
In [3]: x = test() # man sieht wo x ueberschrieben wird
1
In [4]: x
Out[4]: 1
5.4. Vordefinierte Funktionen¶
Python hat eine Reihe von sogenannten Built-in Functions. Hier eine (unvollständige) Liste:
abs() bool() chr() complex() dict()
enumerate() float() globals() help() list()
input() int() id() len() pow()
locals() max() min() open()
range() repr() round() sorted()
str() sum() tuple() type()
5.5. Übungsbeispiele¶
- Aufgabe 5.1
- Implementieren Sie eine Funktion
berechne_Distanz(A,B)
, welche die Distanz zwischen 2 PunktenA
undB
zurueck gibt.A
undB
sind Punkte im 3-dimensionalen Raum, die als Liste mit Länge 3 dargestellt werden. - Schreiben Sie eine Funktion
minimale_Distanz(Liste)
, welcher eine Liste von Positionen (dargestellt jeweils als Listen mit Länge 3) übergeben bekommt. Die Funktion sucht den minimalen Abstand zwischen 2 dieser Punkte. Zurückgegeben werden soll eine neue Liste. Diese Liste soll als 1. Wert die minimale Distanz zwischen 2 Punkten und als 2. und 3. Wert die Indizes der dazugehörigen Punkte enthalten.
- Implementieren Sie eine Funktion
- Aufgabe 5.2
Schreiben Sie eine Funktion
Bearbeite
mit den ArgumentenDict
(ein Dictionary),String
(eine Zeichenkette), sowie dem optionalen ArgumentKopie
mit dem Default WertFalse
.Verdoppeln Sie den String in der Funktion.
Unterscheiden Sie:
- Wenn
Kopie
den WertFalse
hat, soll in das DictionaryDict
der Schlüssel2
mit dem zugehörigen verdoppelten String als Wert eingefügt werden. - Wenn
Kopie
den WertTrue
hat, soll eine Kopie von dem Dictionary angelegt werden und der Schlüssel2
mit dem zugehörigen verdoppelten String als Wert in das kopierte Dictionary eingefügt werden.
- Wenn
Geben Sie anschließend das modifizierte Dictionary, sowie den modifizierten String zurück.
Definieren Sie zum Testen Ihrer Funktion im Hauptprogramm das folgenden Dictionary
Dict
, sowie die folgenden ZeichenketteString
:Dict = {1 :'Katze', 2:'ElefantElefant',3:'MausMausMaus'} String = 'Hund'
Rufen Sie Ihre Funktion im Hauptprogramm mit dem Argument
Kopie=False
auf und testen Sie, ob sich das Dictionary und der String nach dem Funktionsaufruf verändert haben.Wiederholen Sie die Punkte 5) und 6), aber verwenden Sie diesmal das Argument
Kopie=True
. Überprüfen Sie erneut, ob sich das Dictionary und der String verändert haben.Beantworten Sie die folgenden Fragen als Kommentar in Ihrem Skript: Sind Dictionaries bzw. Strings veränderliche oder unveränderliche Datentypen? In welchem Fall wird einer Funktion daher nur der Wert (also call-by-value) und wann das ganze Objekt (also call-by-reference) übergeben?
5.6. weitere Übungsbeispiele¶
- Aufgabe 5.3
- Schreiben Sie eine Funktion
Negiere(A)
mit einem Vektor oder einer MatrizeA
als Parameter. Die Funktion soll jeden Eintrag vonA
negieren, d.h. mit-1
multiplizieren. Die Funktion soll das übergebeneA
nicht verändern, sondern einen neuen Vektor / eine neue Matrize erstellen und zurückgeben.Vektoren werden hier als Listen und Matrizen als Listen von Listen dargestellt (dabei repräsentiert jede innere Liste jeweils eine Zeile der Matrize).
Hinweis
- Eine Möglichkeit um zu überpruefen ob es sich bei
A
um eine Liste oder eine Matrize handelt, ist die Art eines Eintrags z.B. mittelstype(A[0])
zu überpruefen. - Wenn Sie eine Kopie von einer Matrix anlegen, achten Sie darauf, dass auch die inneren Listen explizit kopiert werden!
Der Aufruf der Funktion könnte wie folgt aussehen:
A = [1,2] Aneg = Negiere(A) print(Aneg) #-> Ausgabe: [-1,-2] print(A) #-> Ausgabe: [1,2] B = [[1.2,2],[3,4.5]] Bneg = Negiere(B) print(Bneg) #-> Ausgabe: [[-1.2,-2],[-3,-4.5]] print(B) #-> Ausgabe: [[1.2,2],[3,4.5]]
- Eine Möglichkeit um zu überpruefen ob es sich bei
- Aufgabe 5.4
Schreiben Sie 2 Funktionen zur Addition von Matrizen bzw Vektoren. Vektoren sollen dabei als Listen und Matrizen als Listen von Listen dargestellt werden (dabei repräsentiert jede innere Liste jeweils eine Zeile der Matrize).
1.) Eine Funktion
AddAssign(A,B)
, welcheA
undB
addiert und das Ergebnis auf A speichert. Nach erfolgreicher Addition wird True zurückgegeben. D.h. der AufrufAddAssign(A,B)
entsprichtA += B
.2.) Eine Funktion
Add(A,B)
, welche A und B addiert und das Ergebnis zurückgibt. A und B sollen sich dabei nicht verändern. D.h. der AufrufC = Add(A,B)
entsprichtC = A + B
.
Hinweis
Überprüfen Sie zunächst ob die Dimension der Matrizen bzw. Vektoren zusammenpassen und geben Sie False zurück falls das Produkt nicht berechnet werden kann.
- Aufgabe 5.5
Schreiben Sie eine Funktion
Obstkorb(Fruechte)
, welche als Parameter einen StringFruechte
übergeben bekommt.Fruechte
enthält eine beliebige Anzahl an Obstnamen, die durch Strichpunkte (;) getrennt sind. Ein Obstname kann dabei auch mehrmals auftreten.Die Funktion
Obstkorb
soll nun ein Dictionary erstellen, mit dem Obstnamen als Schlüssel und der Anzahl der jeweiligen Frucht als Wert. Das Dictionary wird von der Funktion zurückgegeben.Der Aufruf der Funktion könnte wie folgt aussehen:
Fruechte = "Banane; Apfel; Banane; Birne" print(Obstkorb(Fruechte)) -> Ausgabe: {'Birne': 1, 'Banane': 2, 'Apfel': 1} (Die Reihenfolge der Elemente in der Ausgabe ist beliebig.)
Funktionsname: ObstkorbParameter: stringRueckgabewert: Dictionary (keys: strings, values: int)