Funktionen

Ihr kennt das Konzept der Funktion bereits aus den Übungsaufgaben.

Funktionen (in anderen Programmiersprachen auch Subroutinen oder Prozeduren genannt) sind eine Sammlung von Einzelbefehlen. Sie erlauben

  • vereinfachte Mehrfachverwendung durch Aufruf des Funktionsnamens
  • verbesserte Code-Lesbarkeit
  • Kennzeichnung der zugehörigen Eingabe- und Ausgabegrößen
  • Zugriff auf große Befehlssammlungen in externen Paketen und Bibliotheken

Damit entsprechen Funktionen direkt der DRY-Philosophie.

Eine Reihe von Funktionen gehören zum Grundinventar von Python, andere können wir uns durch das Einbinden von Paketen dazuholen, oder wir machen uns unsere eigenen Funktionen!

Beispiele für Funktionen aus Python-Paketen

Üblicherweise geben Funktionen etwas zurück (in der Funktion gibt es ein return-Statement).

In [2]:
import os
In [3]:
# Die Funktion os.getcwd() gibt das aktuelle Arbeitsverzeichnis zurück
os.getcwd()
Out[3]:
'/media/x/teach/MSc_UmweltDV/umweltdv/03_controlflow_functions/python'
In [5]:
import datetime
In [6]:
# Die Funktion datetime.now() gibt die aktuelle Systemzeit zurück
datetime.datetime.now()
Out[6]:
datetime.datetime(2021, 11, 18, 16, 42, 36, 624902)
In [8]:
import math
In [10]:
# Die Funktion math.sin gibt den Sinus eines Inputs (Radian/Bogenmaß) zurück
math.sin(1)
Out[10]:
0.8414709848078965

Am letzten Beispiel sehen wir:

  • Funktion können etwas zurückgeben (Output),
  • und sie können etwas entgegennehmen (synonym: Input, Argumente, Parameter)

Benannte Argumente

Python erlaubt, die Argumente einer Funktion mit Namen zu versehen:

In [12]:
import random
In [14]:
random.seed(42)
random.uniform(a=1, b=2)
Out[14]:
1.6394267984578836

Das Ergebnis ist identisch zu:

In [16]:
random.seed(42)
random.uniform(1,2)
Out[16]:
1.6394267984578836

aber unterscheidet sich von

In [17]:
random.seed(42)
random.uniform(b=1, a=2)
Out[17]:
1.3605732015421164

Bei unbenannten Argumenten ermittelt Python die Bedeutung der Argumente aus ihrer Reihenfolge. Manchmal erleichtert es die Lesbarkeit des Codes, die Argumente zu benennen.

Selbstdefinierte Funktionen - die Helfer im Alltag

Eine eigene Funktion, die lästige wiederkehrende Arbeit vereinfacht, könnte so angelegt werden:

In [18]:
# Einfache Funktion ohne Argumente und Rückgabewert
def abwasch():
    print("Gläser spülen")
    print("Teller spülen")
    print("Besteck spülen")
In [19]:
type(abwasch)
Out[19]:
function

Diese Funktionsdefinition enthält den Namen der Funktion (abwasch), das Zuweisungszeichen und ein Schlüsselwort (def) sowie den eigentlich Funktionsrumpf, welcher eingerückt werden muss, wie wir es auch schon von den Blöcken bei if oder den Schleifen kennen.

Nach dem Ausführen des Codes passiert scheinbar zunächst nichts. Aber die Funktion ist nun verfügbar. Wir können uns auch über den Aufruf (mit Klammern) ihres Namens von ihrer Existenz überzeugen:

In [20]:
abwasch()
Gläser spülen
Teller spülen
Besteck spülen

Aufgabe: Abwasch-Marathon

Da Hausarbeit somit ihren Schrecken verloren hat, wasche doch bitte gleich die dreifache Menge ab!

Lösung

In [12]:
abwasch()
abwasch()
abwasch()

# oder eleganter:

for i in range(0,3):
    abwasch()
Gläser spülen
Teller spülen
Besteck spülen
Gläser spülen
Teller spülen
Besteck spülen
Gläser spülen
Teller spülen
Besteck spülen
Gläser spülen
Teller spülen
Besteck spülen
Gläser spülen
Teller spülen
Besteck spülen
Gläser spülen
Teller spülen
Besteck spülen

Argumente

Damit die Funktion auf veränderte Bedingungen reagieren kann, sollte sie Eingabedaten über ihre Argumente berücksichtigen:

In [21]:
# Funktion mit Argument
def abwasch2(was_noch):
    print("Gläser spülen")
    print("Teller spülen")
    print("Besteck spülen")
    print(was_noch +  " spülen")

Aufgabe: Abwaschen der Töpfe I

Lasst uns nun zusätzlich noch Töpfe abspülen! Wie muss der Aufruf der Funktion aussehen?

Lösung

In [14]:
abwasch2("Töpfe") # Funktion mit Argument benutzen
Gläser spülen
Teller spülen
Besteck spülen
Töpfe spülen

Aufgabe: Abwaschen der Töpfe II

Noch flexibler wird die Funktion, wenn man ihr einen Vektor von Zeichenketten übergeben kann, von denen jede dann einzeln abgewaschen wird. Wie muss die Funktion erweitert werden?

Lösung

In [22]:
def abwasch2_viele(was_noch):
    print("Gläser spülen")
    print("Teller spülen")
    print("Besteck spülen")
    for teil in was_noch:
        print(teil +  " spülen")
        
abwasch2_viele(["Untertassen", "Haare", "Hund"])
Gläser spülen
Teller spülen
Besteck spülen
Untertassen spülen
Haare spülen
Hund spülen

Rückgabewerte von Funktionen

Unsere Funktion soll nun ein Ergebnis zurückgeben. In unserem Fall soll dies der Wert sauberkeit sein, die als Zufallszahl gezogen wird. Die Rückgabe des Wertes erfolgt mit dem Befehl return(). Mit diesem wird die Funktion auch verlassen; nachfolgende Befehle werden dann nicht mehr ausgeführt.

In [28]:
#Funktion mit Argument und Rückgabewert
def abwasch3(was_noch=[]):
    print("Gläser spülen")
    print("Teller spülen")
    print("Besteck spülen")
    for teil in was_noch:
        print(teil +  " spülen")
    # eine Zufallszahl zwischen 1 und 10
    sauberkeit = random.uniform(1,10)
    return(sauberkeit)
    # Dieser Befehl wird nicht mehr erreicht
    print("Spülbecken putzen")
In [30]:
# Funktion mit Argument benutzen, Rückgabewert in Variable speichern
sauberkeit = abwasch3(["Töpfe"])
print("Sauberkeit: " + str(sauberkeit)) 
Gläser spülen
Teller spülen
Besteck spülen
Töpfe spülen
Sauberkeit: 3.4752638653220735

Aufgabe: Abwaschwasser wechseln

Wasche ab, auch die Pfanne. Wenn die Sauberkeit unter 5 ist, gib die Meldung "Abwaschwasser wechseln!" aus, andernfalls melde "Fertig!"

Lösung

In [31]:
sauberkeit = abwasch3(["Pfanne"])

if sauberkeit < 5:
    print("Abwaschwasser wechseln!")
else:
    print("Fertig!")
Gläser spülen
Teller spülen
Besteck spülen
Pfanne spülen
Abwaschwasser wechseln!

Optional: Optionale Funktionsargumente

Funktionsargumente können auch optional sein:

In [23]:
range(0,20,3)
print(list(range(0,20,3))) # das dritte Argument gibt die Intervalgröße an.
[0, 3, 6, 9, 12, 15, 18]
In [24]:
?range

In der Hilfe zu den Funktionen (?range) werden optionale Argumente mit ihrem voreingestellten Wert aufgeführt. In der selben Art können wir auch für unsere eigene Funktion optionale Argumenten erlauben.

Optionale Aufgabe: Abwasch mit optionalem Mehr

Verändere die Funktion abwasch2() so, dass ihr Argument optional wird. Die entsprechende Ausgabe soll nur erfolgen, wenn das Argument gesetzt ist. Wird ein optionales Argument nicht übergeben, nimmt es den Wert an, der in der Funktionsdeklaration angegeben wurde. In diesem Fall geben wir eine leere Liste als default mit...

Lösung

In [26]:
def abwasch2_viele(was_noch=[]):
    print("Gläser spülen")
    print("Teller spülen")
    print("Besteck spülen")
    for teil in was_noch:
        print(teil +  " spülen")
In [27]:
# Basisspülprogramm
abwasch2_viele() 
# mit Extraspülung
abwasch2_viele(["Toilette"])
Gläser spülen
Teller spülen
Besteck spülen
Gläser spülen
Teller spülen
Besteck spülen
Toilette spülen

Optional: Funktionen verketten

Funktionen lassen sich im Hauptprogramm aufrufen, können aber auch innerhalb anderer Funktionen ausgerufen werden. Es ist sogar möglich, dass sich Funktionen selbst aufrufen (rekursive Funktionen).

Aufgabe: Temperaturumrechnung I

Schreibe eine Funktion fahr_zu_kelvin(), die die Temperatur von Fahrenheit in Kelvin umrechnet $(T_{Kelv} = ((T_{Fahr} - 32) \cdot (5 / 9)) + 273.15)$!

Lösung

In [33]:
def fahr_zu_kelvin(temp_f):
    temp_k = ((temp_f - 32) * (5 / 9)) + 273.15
    return(temp_k)
In [34]:
# Gefrierpunkt von Wasser
fahr_zu_kelvin(32)
Out[34]:
273.15
In [25]:
# Siedepunkt von Wasser
fahr_zu_kelvin(212)
Out[25]:
373.15

Aufgabe: Temperaturumrechnung II

Schreibe eine Funktion kelvin_zu_celsius(), die die Temperatur von Kelvin in °Celsius umrechnet ($T_{Cels} = ((T_{Kelv} - 273.15$)!

Lösung

In [26]:
def kelvin_zu_celsius(temp_k):
    temp_c = temp_k - 273.15
    return(temp_c)
In [27]:
#absoluter Nullpunkt
kelvin_zu_celsius(0)
Out[27]:
-273.15

Aufgabe: Temperaturumrechnung III

Schreibe nun eine Funktion, die Fahrenheit in Celsius umrechnet. Verwende dabei die beiden bestehenden Funktionen fahr_zu_kelvin und kelvin_zu_celsius!

Lösung

In [28]:
def fahr_zu_celsius(temp_f):
    temp_k = fahr_zu_kelvin(temp_f)
    temp_c = kelvin_zu_celsius(temp_k)
    return(temp_c)

# Gefrierpunkt von Wasser
fahr_zu_celsius(32)
Out[28]:
0.0

Optional: Gültigkeit und Sichtbarkeitsbereich

Beim LESEN von Variablen (und auch Funktionen) wird i.d.R. "von innen nach außen" ausgewertet. Zuweisungen bleiben lokal (also in der Funktion). Hä?

Ein Beispiel:

In [37]:
X = "externer Wert von X"   #eine Zuweisung zur EXTERNEN /globalen Variablen X
Y = "externer Wert von Y"   #eine Zuweisung zur INTERNEN Variablen Y

print("Auswertung außen:")
print("X: " + X)
print("Y: " + Y)
Auswertung außen:
X: externer Wert von X
Y: externer Wert von Y
In [38]:
def meine_funktion():
    X = "interner Wert von X"      #eine Zuweisung zur INTERNEN Variablen X
    Z ="interner Wert von Z"      #eine Zuweisung zur INTERNEN Variablen Z
    print("Auswertung innen:")
    print("X: " + X)
    print("Y: " +  Y)
    print("Z: " + Z)
In [39]:
meine_funktion()
print(Z)
Auswertung innen:
X: interner Wert von X
Y: externer Wert von Y
Z: interner Wert von Z
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_18256/3520972276.py in <module>
      1 meine_funktion()
----> 2 print(Z)

NameError: name 'Z' is not defined

Das heißt:

  • Variable X existiert praktisch doppelt, extern und intern. Die innere Zuweisung beeinflusst den Wert außen nicht.
  • Variable Y wird nur außen zugewiesen, ist aber dennoch innerhalb der Funktion lesbar
  • Variable Z wird nur innen zugewiesen. Außerhalb ist sie gar nicht verfügbar (Fehlermeldung).

In [ ]: