.. include:: markup.rst ********** Funktionen ********** :mark:`Funktionen` sind Unterprogramme und helfen die * :mark:`Les- und Wartbarkeit` bei großen Programmen zu erleichtern (durch Unterteilung in Unterprogramme) sowie * :mark:`Redundanzen` zu vermeiden (wiederkehrende Algorithmen nur 1x implementiert). Diese * benötigen i.A. Eingaben - sog. :mark:`Parameter` (:mark:`Argumente`) - und * liefern (damit berechnete) Ergebnisse zurück, genannt :mark:`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. .. Ein :mark:`Hauptprogramm` (``main``-Funktion) verwaltet die Unterprogramme. Erste Schritte ============== Eine Funktion startet in Python mit dem :mark:`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 :mark:`Anweisungskopf` übergeben. Der :mark:`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 * :mark:`kein Befehl ruft diese Funktion` auf! Dieses Skript muss entsprechend erweitert werden, damit es die Funktion aufruft (``funktion_fak.py``): .. literalinclude:: src/funktion_fak.py 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) .. note:: * Die ``while`` Anweisung ist eine Endlosschleife, nicht gut, fürs Beispiel ok. * Die Kommunikation funktioniert über :mark:`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, mit ``cd`` wechseln man Verzeichnisses) Funktionsparameter ================== 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``): .. literalinclude:: src/funktion_add.py 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 .. note:: * ``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 ist ``y=5`` (Default Wert). Es gibt unterschiedliche :mark:`Möglichkeiten des Aufrufes`. Zunächst eine Funktionsdefinition: Beispiel (``funktion_sumsub.py``): .. literalinclude:: src/funktion_sumsub.py 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 :mark:`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 :mark:`Schlüsselwortparameter` (:mark:`keyword arguments`) .. hint :: Der Aufruf ``#2`` ist zu vermeiden da fehleranfällig. Besser ist ``#3`` aber mit gleicher Reihenfolge wie bei der Funktionsdefinition ``c=27,d=23``! call-by-value / reference ~~~~~~~~~~~~~~~~~~~~~~~~~ Je nach Programmiersprache gibt es unterschiedliche Möglichkeiten wie Argumente übergeben werden: * :mark:`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. * :mark:`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. :mark:`Python` verwendet diesbezüglich eine :mark:`Mischform`. Eine :mark:`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) :mark:`Veränderung der Liste` im Hauptprogramm. :mark:`Vermeidung` einfach durch :mark:`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 :mark:`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 :mark:`Vor- und Nachteile ideal zu kombinieren`. Docstring ~~~~~~~~~ Möchte man eine :mark:`Funktion dokumentieren`, schreibt man direkt nach dem Anweisungskopf einen Kommentar welcher mit ``"""`` (dreifaches Anführungszeichen) startet und endet. Den Text dazwischen nennt man :mark:`docstring`. Beispiel Funktion (``funktion_add.py``): .. literalinclude:: src/funktion_add.py Den :mark:`docstring` kann man folgendermaßen :mark:`ausgeben`: :: In [1]: runfile("funktion_add.py") In [2]: add.__doc__ Out[2]: 'Gib x plus y zurueck.' Die :mark:`magische Funktion` ``__doc__`` retourniert den :mark:`docstring`. Namensräume =========== Innerhalb eines Programmes gibt es einen Satz von :mark:`Variablen`. Diese sieht man in :mark:`Spyder` direkt. Diesen Variablen sind sogenannten :mark:`Namensräumen` zugewiesen in dem diese Werte definiert wurden. Es gibt eigene Namensräume (:mark:`namespaces`) z.B. innerhalb: * Hauptprogramm (global), * Funktionskörper (lokal), * Klassendefinitionen (lokal, siehe später). .. figure:: images/types_namespace-1.png :scale: 30 % :align: center Die Funktionen ``globals()`` und ``locals()`` zeigen den Inhalt dieser Namensräume. Beispiel: Zunächst definieren wir eine Funktion im File (``funktion_namespaces.py``): .. literalinclude:: src/funktion_namespaces.py 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': , '_i3': 'globals()'} Man sieht folgendes : * ``globals()`` gibt ein ``dict`` zurück. Es gibt schon :mark:`vordefinierte` Wertepaare. * Mit ``runfile`` wurde aus ``funktion_namespaces.py`` die Funkion ``check`` geladen (sowie viele andere Dinge). Dadurch kann diese Funktion erst aufgerufen werden mit: :: In [4]: check() {'x': 123} {'__name__': '__main__', ... 'check': } 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 .. warning:: 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 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() Übungsbeispiele ================= **Aufgabe 5.1** 1) Implementieren Sie eine Funktion ``berechne_Distanz(A,B)``, welche die Distanz zwischen 2 Punkten ``A`` und ``B`` zurueck gibt. ``A`` und ``B`` sind Punkte im 3-dimensionalen Raum, die als Liste mit Länge 3 dargestellt werden. 2) 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. **Aufgabe 5.2** 1) Schreiben Sie eine Funktion ``Bearbeite`` mit den Argumenten ``Dict`` (ein Dictionary), ``String`` (eine Zeichenkette), sowie dem optionalen Argument ``Kopie`` mit dem Default Wert ``False``. 2) Verdoppeln Sie den String in der Funktion. 3) Unterscheiden Sie: * Wenn ``Kopie`` den Wert ``False`` hat, soll in das Dictionary ``Dict`` der Schlüssel ``2`` mit dem zugehörigen verdoppelten String als Wert eingefügt werden. * Wenn ``Kopie`` den Wert ``True`` hat, soll eine Kopie von dem Dictionary angelegt werden und der Schlüssel ``2`` mit dem zugehörigen verdoppelten String als Wert in das kopierte Dictionary eingefügt werden. 4) Geben Sie anschließend das modifizierte Dictionary, sowie den modifizierten String zurück. 5) Definieren Sie zum Testen Ihrer Funktion im Hauptprogramm das folgenden Dictionary ``Dict``, sowie die folgenden Zeichenkette ``String``: :: Dict = {1 :'Katze', 2:'ElefantElefant',3:'MausMausMaus'} String = 'Hund' 6) 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. 7) 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. 8) 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? weitere Übungsbeispiele ================= **Aufgabe 5.3** Schreiben Sie eine Funktion ``Negiere(A)`` mit einem Vektor oder einer Matrize ``A`` als Parameter. Die Funktion soll jeden Eintrag von ``A`` negieren, d.h. mit ``-1`` multiplizieren. Die Funktion soll das übergebene ``A`` 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). .. hint :: * 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. mittels ``type(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]] **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)``, welche ``A`` und ``B`` addiert und das Ergebnis auf A speichert. Nach erfolgreicher Addition wird True zurückgegeben. D.h. der Aufruf ``AddAssign(A,B)`` entspricht ``A += 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 Aufruf ``C = Add(A,B)`` entspricht ``C = A + B``. .. hint:: Ü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 String ``Fruechte`` ü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: Obstkorb | Parameter: string | Rueckgabewert: Dictionary (keys: strings, values: int)