19.3 Funktionsparameter
Wir haben bereits oberflächlich besprochen, was Funktionsparameter sind und wie sie verwendet werden können, doch das ist bei Weitem noch nicht die ganze Wahrheit. In diesem Abschnitt werden Sie drei alternative Techniken zur Übergabe von Funktionsparametern kennenlernen.
19.3.1 Optionale Parameter
Zu Beginn dieses Kapitels wurde die Verwendung einer Funktion anhand der Built-in Function range erklärt. Sicherlich wissen Sie aus Abschnitt 5.2.6 über die for-Schleife noch, dass der letzte der drei Parameter der range-Funktion optional ist. Das bedeutet zunächst einmal, dass dieser Parameter beim Funktionsaufruf weggelassen werden kann. Ein optionaler Parameter muss funktionsintern mit einem Wert vorbelegt sein, üblicherweise einem Standardwert, der in einem Großteil der Funktionsaufrufe ausreichend ist. Bei der Funktion range regelt der dritte Parameter die Schrittweite und ist mit 1 vorbelegt. Folgende Aufrufe von range sind also äquivalent:
- range(2, 10, 1)
- range(2, 10)
Dies ist interessant, denn oftmals hat eine Funktion ein Standardverhalten, das sich durch zusätzliche Parameter an spezielle Gegebenheiten anpassen lassen soll. In den überwiegenden Fällen, in denen das Standardverhalten jedoch genügt, wäre es umständlich, trotzdem die für diesen Aufruf überflüssigen Parameter anzugeben. Deswegen sind vordefinierte Parameterwerte oft eine sinnvolle Ergänzung einer Funktionsschnittstelle.
Um einen Funktionsparameter mit einem Default-Wert vorzubelegen, wird dieser Wert bei der Funktionsdefinition zusammen mit einem Gleichheitszeichen hinter den Parameternamen geschrieben. Die folgende Funktion soll je nach Anwendung die Summe von zwei, drei oder vier ganzen Zahlen berechnen und das Ergebnis zurückgeben. Dabei soll der Programmierer beim Aufruf der Funktion nur so viele Zahlen angeben müssen, wie er benötigt:
>>> def summe(a, b, c=0, d=0):
... return a + b + c + d
Um eine Addition durchzuführen, müssen mindestens zwei Parameter übergeben worden sein. Die anderen beiden werden mit 0 vorbelegt. Sollten sie beim Funktionsaufruf nicht explizit angegeben werden, fließen sie nicht in die Addition ein. Die Funktion kann folgendermaßen aufgerufen werden:
>>> summe(1, 2)
3
>>> summe(1, 2, 3)
6
>>> summe(1, 2, 3, 4)
10
Beachten Sie, dass optionale Parameter nur am Ende einer Funktionsschnittstelle stehen dürfen. Das heißt, dass auf einen optionalen kein nicht-optionaler Parameter mehr folgen darf. Diese Einschränkung ist wichtig, damit alle angegebenen Parameter eindeutig zugeordnet werden können.
19.3.2 Schlüsselwortparameter
Neben den bislang verwendeten sogenannten Positional Arguments (Positionsparameter) gibt es in Python eine weitere Möglichkeit, Parameter zu übergeben. Solche Parameter werden Keyword Arguments (Schlüsselwortparameter) genannt. Es handelt sich dabei um eine alternative Technik, Parameter beim Funktionsaufruf zu übergeben. An der Funktionsdefinition ändert sich nichts. Betrachten wir dazu unsere Summenfunktion, die wir im vorangegangenen Abschnitt geschrieben haben:
>>> def summe(a, b, c=0, d=0):
... return a + b + c + d
Diese Funktion kann auch folgendermaßen aufgerufen werden:
>>> summe(d=1, b=3, c=2, a=1)
7
Dazu werden im Funktionsaufruf die Parameter wie bei einer Zuweisung auf den gewünschten Wert gesetzt. Da bei der Übergabe der jeweilige Parametername angegeben werden muss, ist die Zuordnung unter allen Umständen eindeutig. Das erlaubt es dem Programmierer, Schlüsselwortparameter in beliebiger Reihenfolge anzugeben.
Es ist möglich, beide Formen der Parameterübergabe zu kombinieren. Dabei ist zu beachten, dass keine Positional Arguments auf Keyword Arguments folgen dürfen, Letztere also immer am Ende des Funktionsaufrufs stehen müssen.
>>> summe(1, 2, c=10, d=11)
24
Beachten Sie außerdem, dass nur solche Parameter als Keyword Arguments übergeben werden dürfen, die im selben Funktionsaufruf nicht bereits als Positional Arguments übergeben wurden.
19.3.3 Beliebige Anzahl von Parametern
Rufen Sie sich noch einmal die Verwendung der eingebauten Funktion print in Erinnerung:
>>> print("P")
P
>>> print("P", "y", "t", "h", "o", "n")
P y t h o n
>>> print("P", "y", "t", "h", "o", "n", " ", "i", "s", "t", " ",
... "s", "u", "p", "e", "r")
P y t h o n i s t s u p e r
Offensichtlich ist es möglich, der Funktion print eine beliebige Anzahl von Parametern zu übergeben. Diese Eigenschaft ist nicht exklusiv für die print-Funktion, sondern es können auch eigene Funktionen definiert werden, denen beliebig viele Parameter übergeben werden können.
Für beide Formen der Parameterübergabe (Positional und Keyword) gibt es eine Notation, die es einer Funktion ermöglicht, beliebig viele Parameter entgegenzunehmen. Bleiben wir zunächst einmal bei den Positional Arguments. Betrachten Sie dazu folgende Funktionsdefinition:
>>> def funktion(a, b, *weitere):
... print("Feste Parameter:", a, b)
... print("Weitere Parameter:", weitere)
...
Zunächst einmal werden ganz klassisch zwei Parameter a und b festgelegt und zusätzlich ein dritter namens weitere. Wichtig ist der Stern vor seinem Namen. Bei einem Aufruf dieser Funktion würden a und b, wie Sie das bereits kennen, die ersten beiden übergebenen Instanzen referenzieren. Interessant ist, dass weitere fortan ein Tupel referenziert, das alle zusätzlich übergebenen Instanzen enthält. Anschaulich wird dies, wenn wir folgende Funktionsaufrufe betrachten:
>>> funktion(1, 2)
Feste Parameter: 1 2
Weitere Parameter: ()
>>> funktion(1, 2, "Hallo Welt", 42, [1,2,3,4])
Feste Parameter: 1 2
Weitere Parameter: ('Hallo Welt', 42, [1, 2, 3, 4])
Der Parameter weitere referenziert also beim ersten Aufruf ein leeres Tupel und beim zweiten Aufruf ein Tupel, in dem alle über a und b hinausgehenden Instanzen in der Reihenfolge enthalten sind, in der sie übergeben wurden.
An dieser Stelle möchten wir die im vorangegangenen Beispiel definierte Funktion summe dahingehend erweitern, dass sie die Summe einer vom Benutzer festgelegten Zahl von Parametern berechnen kann:
>>> def summe(*parameter):
... s = 0
... for p in parameter:
... s += p
... return s
Das folgende Beispiel demonstriert die Anwendung der weiterentwickelten Funktion summe im interaktiven Modus:
>>> summe(1, 2, 3, 4, 5)
15
>>> summe(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
78
Diese Art, einer Funktion das Entgegennehmen beliebig vieler Parameter zu ermöglichen, funktioniert ebenso für Keyword Arguments. Der Unterschied besteht darin, dass der Parameter, der alle weiteren Instanzen enthalten soll, in der Funktionsdefinition mit zwei Sternen geschrieben werden muss. Außerdem referenziert er später kein Tupel, sondern ein Dictionary. Dieses Dictionary enthält den jeweiligen Parameternamen als Schlüssel und die übergebene Instanz als Wert. Betrachten Sie dazu folgende Funktionsdefinition:
>>> def funktion(a, b, **weitere):
... print("Feste Parameter:", a, b)
... print("Weitere Parameter:", weitere)
und diese beiden dazu passenden Funktionsaufrufe:
>>> funktion(1, 2)
Feste Parameter: 1 2
Weitere Parameter: {}
>>> funktion(1, 2, johannes="ernesti", peter="kaiser")
Feste Parameter: 1 2
Weitere Parameter: {'johannes': 'ernesti', 'peter': 'kaiser'}
Der Parameter weitere referenziert also ein Dictionary, das alle übergebenen Schlüsselwortparameter mit Wert enthält.
Beide Techniken zum Entgegennehmen beliebig vieler Parameter können zusammen verwendet werden, wie folgende Funktionsdefinition zeigt:
>>> def funktion(*positional, **keyword):
... print("Positional:", positional)
... print("Keyword:", keyword)
>>> funktion(1, 2, 3, 4, hallo="welt", key="word")
Positional: (1, 2, 3, 4)
Keyword: {'hallo': 'welt', 'key': 'word'}
Sie sehen, dass positional ein Tupel mit allen Positions- und keyword ein Dictionary mit allen Schlüsselwortparametern referenziert.
19.3.4 Reine Schlüsselwortparameter
Es ist möglich, Parameter zu definieren, die ausschließlich in Form von Schlüsselwortparametern übergeben werden dürfen. Solche reinen Schlüsselwortparameter[ 64 ](engl. keyword-only parameters) werden bei der Funktionsdefinition nach dem Parameter geschrieben, der beliebig viele Positionsargumente aufnimmt:
>>> def f(a, b, *c, d, e):
... print(a, b, c, d, e)
In diesem Fall besteht die Funktionsschnittstelle aus den beiden Positionsparametern a und b, der Möglichkeit für weitere Positionsparameter *c und den beiden reinen Schlüsselwortparametern d und e. Es gibt keine Möglichkeit, die Parameter d und e zu übergeben, außer in Form von Schlüsselwortparametern.
>>> f(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() missing 2 required keyword-only arguments: 'd' and 'e'
>>> f(1, 2, 3, 4, 5, d=4, e=5)
1 2 (3, 4, 5) 4 5
Wie bei Positionsparametern müssen reine Schlüsselwortparameter angegeben werden, sofern sie nicht mit einem Default-Wert belegt sind:
>>> def f(a, b, *c, d=4, e=5):
... print(a, b, c, d, e)
...
>>> f(1, 2, 3)
1 2 (3,) 4 5
Wenn zusätzlich die Übergabe beliebig vieler Schlüsselwortparameter ermöglicht werden soll, folgt die dazu notwendige **-Notation nach den reinen Schlüsselwortparametern am Ende der Funktionsdefinition:
>>> def f(a, b, *args, d, e, **kwargs):
... print(a, b, args, d, e, kwargs)
Es ist auch möglich, reine Schlüsselwortparameter zu definieren, ohne gleichzeitig beliebig viele Positionsparameter zuzulassen. Dazu werden die reinen Schlüsselwortparameter in der Funktionsschnittstelle durch einen * von den Positionsparametern getrennt.
>>> def f(a, b, *, c, d):
... print(a, b, c, d)
...
>>> f(1, 2, 3, 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 4 were given
>>> f(1, 2, c=3, d=4)
1 2 3 4
19.3.5 Entpacken einer Parameterliste
In diesem Abschnitt lernen Sie eine weitere Möglichkeit kennen, Parameter an eine Funktion zu übergeben. Dazu stellen wir uns vor, wir wollten mithilfe der in Abschnitt 19.3.3, »Beliebige Anzahl von Parametern«, definierten erweiterten Version der summe-Funktion die Summe aller Einträge eines Tupels bestimmen. Dazu ist momentan die folgende Notation nötig:
>>> t = (1, 4, 3, 7, 9, 2)
>>> summe(t[0], t[1], t[2], t[3], t[4], t[5])
26
Das ist sehr umständlich. Zudem laufen wir der Allgemeinheit der Funktion summe zuwider, denn die Anzahl der Elemente des Tupels t muss stets bekannt sein. Wünschenswert ist ein Weg, eine in einem iterierbaren Objekt gespeicherte Liste von Argumenten direkt einer Funktion übergeben zu können. Dieser Vorgang wird Entpacken genannt.
Das Entpacken eines iterierbaren Objekts geschieht dadurch, dass der Funktion das Objekt mit einem vorangestellten Sternchen (*) übergeben wird. Im folgenden Beispiel wird das von der eingebauten Funktion range erzeugte iterierbare Objekt verwendet, um mithilfe der Funktion summe die Summe der ersten 100 natürlichen Zahlen zu berechnen:[ 65 ](Das beim Funktionsaufruf von range(n) zurückgegebene iterierbare Objekt durchläuft alle ganzen Zahlen von 0 bis einschließlich n–1. Daher muss im Beispiel 101 anstelle von 100 übergeben werden. )
>>> summe(*range(101))
5050
Beim Funktionsaufruf wird der Funktion jedes Element des iterierbaren Objekts, in diesem Fall also die Zahlen von 0 bis 100, als gesonderter Parameter übergeben. Das Entpacken einer Parameterliste funktioniert nicht nur im Zusammenhang mit einer Funktion, die beliebig viele Parameter erwartet, sondern kann auch mit der ursprünglichen Funktion summe, die die Summe von maximal vier Parametern bestimmt, verwendet werden:
>>> def summe(a, b, c=0, d=0):
... return a + b + c + d
Beachten Sie dabei, dass das zu entpackende iterierbare Objekt auch maximal vier (und mindestens zwei) Elemente bereitstellt:
>>> t = (6, 3, 9, 12)
>>> summe(*t)
30
>>> summe(*[4, 6, 12, 7, 9])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: summe() takes from 2 to 4 positional arguments but 5 were given
Analog zum Entpacken eines Tupels zu einer Liste von Positionsparametern kann ein Dictionary zu einer Liste von Schlüsselwortparametern entpackt werden. Der Unterschied in der Notation besteht darin, dass zum Entpacken eines Dictionarys zwei Sternchen vorangestellt werden müssen:
>>> d = {"a" : 7, "b" : 3, "c" : 4}
>>> summe(**d)
14
Es ist noch zu erwähnen, dass die Techniken zum Entpacken von Parameterlisten miteinander kombiniert werden können, wie folgendes Beispiel zeigt:
>>> summe(1, *(2,3), **{"d" : 4})
10
Seit Python 3.5 ist es möglich, mehrere Sequenzen oder Dictionarys im selben Funktionsaufruf zu entpacken:
>>> summe(*(1,2), *(3,4))
10
>>> summe(*(1,2), **{"c" : 3}, **{"d" : 4})
10
Dabei dürfen Schlüsselwortargumente nicht mehrfach übergeben werden.
Zusätzlich gibt es seit Python 3.5 die Möglichkeit, auch beim Erzeugen von sequenziellen Datentypen, Mengen und Dictionarys auf Packing bzw. Unpacking zurückzugreifen:
>>> A = [1,2,3]
>>> B = [3,4,5]
>>> [1, *A, *B]
[1, 1, 2, 3, 3, 4, 5]
>>> {1, *A, *B}
{1, 2, 3, 4, 5}
>>> (1, *A, *B)
(1, 1, 2, 3, 3, 4, 5)
>>> {"a": 10, **{"b": 11, "c": 12}, "d": 13}
{'a': 10, 'b': 11, 'c': 12, 'd': 13}
>>> {"a": 10, **{"b": 11, "c": 12}, "d": 13, **{"e": 14}}
{'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14}
Wird bei einem Dictionary derselbe Schlüssel mehrfach übergeben, zählt das letzte Schlüssel-Wert-Paar:
>>> {"a": 10, **{"a": 11, "b": 12}, "a": 13, **{"b": 14}}
{'a': 13, 'b': 14}
[»] Hinweis
Generell ist Vorsicht geboten, wenn Unpacking für ungeordnete Datentypen verwendet wird. Im folgenden Beispiel hängt die Reihenfolge der Elemente 1, 2, 3, 4, 5 davon ab, in welcher Reihenfolge über die Menge {3,4,1,2,5} iteriert wird:
>>> [0, *{3, 4, 1, 2, 5}]
[0, 1, 2, 3, 4, 5]
Da diese Reihenfolge ein Implementierungsdetail ist, kann sie sich zwischen verschiedenen Python-Versionen unterscheiden.
19.3.6 Seiteneffekte
Bisher haben wir diese Thematik geschickt umschifft, doch Sie sollten immer im Hinterkopf behalten, dass sogenannte Seiteneffekte (engl. side effects) immer dann auftreten können, wenn eine Instanz eines mutablen Datentyps, also zum Beispiel einer Liste oder eines Dictionarys, als Funktionsparameter übergeben wird.
In Python werden bei einem Funktionsaufruf keine Kopien der als Parameter übergebenen Instanzen erzeugt, sondern es wird funktionsintern mit Referenzen auf die Argumente gearbeitet.[ 66 ](Diese Methode der Parameterübergabe wird Call by Reference genannt. Demgegenüber steht das Prinzip Call by Value, bei dem funktionsintern auf Kopien der Argumente gearbeitet wird. Letztere Variante ist frei von Seiteneffekten, aber aufgrund des Kopierens langsamer. ) Betrachten Sie dazu folgendes Beispiel:
>>> def f(a, b):
... print(id(a))
... print(id(b))
...
>>> p = 1
>>> q = [1,2,3]
>>> id(p)
134537016
>>> id(q)
134537004
>>> f(p, q)
134537016
134537004
Im interaktiven Modus definieren wir zuerst eine Funktion f, die zwei Parameter a und b erwartet und deren jeweilige Identität ausgibt. Anschließend werden zwei Referenzen p und q angelegt, die eine ganze Zahl bzw. eine Liste referenzieren. Dann lassen wir uns die Identitäten der beiden Referenzen ausgeben und rufen die angelegte Funktion f auf. Sie sehen, dass die ausgegebenen Identitäten gleich sind. Es handelt sich also sowohl bei p und q als auch bei a und b im Funktionskörper um Referenzen auf dieselben Instanzen. Dabei macht es zunächst einmal keinen Unterschied, ob die referenzierten Objekte Instanzen eines veränderlichen oder unveränderlichen Datentyps sind.
Trotzdem ist die Verwendung eines unveränderlichen Datentyps grundsätzlich frei von Seiteneffekten, da dieser bei Veränderung automatisch kopiert wird und alte Referenzen davon nicht berührt werden. Sollten wir also beispielsweise a im Funktionskörper um eins erhöhen, werden nachher a und p verschiedene Instanzen referenzieren. Dies führt dazu, dass bei der Verwendung unveränderlicher Datentypen in Funktionsschnittstellen keine Seiteneffekte auftreten können.[ 67 ](Beachten Sie, dass dies nicht für unveränderliche Instanzen gilt, die veränderliche Instanzen enthalten. So können bei der Parameterübergabe eines Tupels, das eine Liste enthält, durchaus Seiteneffekte auftreten. )
Diese Sicherheit können uns veränderliche Datentypen, etwa Listen oder Dictionarys, nicht geben. Dazu folgendes Beispiel:
>>> def f(liste):
... liste[0] = 42
... liste += [5,6,7,8,9]
>>> zahlen = [1,2,3,4]
>>> f(zahlen)
>>> zahlen
[42, 2, 3, 4, 5, 6, 7, 8, 9]
Zunächst wird eine Funktion definiert, die eine Liste als Parameter erwartet und diese im Funktionskörper verändert. Daraufhin wird eine Liste angelegt, die der Funktion als Parameter übergeben und schlussendlich ausgegeben wird. Die Ausgabe zeigt, dass sich die Änderungen an der Liste nicht allein auf den Kontext der Funktion beschränken, sondern sich auch im Hauptprogramm auswirken. Dieses Phänomen wird Seiteneffekt genannt. Wenn eine Funktion nicht nur lesend auf eine Instanz eines veränderlichen Datentyps zugreifen muss und Seiteneffekte nicht ausdrücklich erwünscht sind, sollten Sie innerhalb der Funktion oder bei der Parameterübergabe eine Kopie der Instanz erzeugen. Das kann in Bezug auf das oben genannte Beispiel so aussehen:[ 68 ](Sie erinnern sich, dass beim Slicen einer Liste stets eine Kopie derselben erzeugt wird. Im Beispiel wurde das Slicing ohne Angabe von Start- und Endindex verwendet, um eine vollständige Kopie der Liste zu erzeugen. )
>>> zahlen = [1,2,3,4]
>>> f(zahlen[:])
>>> zahlen
[1, 2, 3, 4]
Neben den bisher besprochenen Referenzparametern gibt es eine weitere, seltenere Form von Seiteneffekten, die auftritt, wenn ein veränderlicher Datentyp als Default-Wert eines Parameters verwendet wird:
>>> def f(a=[1,2,3]):
... a += [4,5]
... print(a)
...
>>> f()
[1, 2, 3, 4, 5]
>>> f()
[1, 2, 3, 4, 5, 4, 5]
>>> f()
[1, 2, 3, 4, 5, 4, 5, 4, 5]
>>> f()
[1, 2, 3, 4, 5, 4, 5, 4, 5, 4, 5]
Wir definieren im interaktiven Modus eine Funktion, die einen einzigen Parameter erwartet, der mit einer Liste vorbelegt ist. Im Funktionskörper wird diese Liste um zwei Elemente vergrößert und ausgegeben. Nach mehrmaligem Aufrufen der Funktion ist zu erkennen, dass es sich bei dem Default-Wert augenscheinlich immer um dieselbe Instanz gehandelt hat und nicht bei jedem Aufruf eine neue Liste mit dem Wert [1, 2, 3] erzeugt wurde.
Das liegt daran, dass eine Instanz, die als Default-Wert genutzt wird, nur einmalig und nicht bei jedem Funktionsaufruf neu erzeugt wird. Grundsätzlich sollten Sie also darauf verzichten, Instanzen veränderlicher Datentypen als Default-Werte zu verwenden. Schreiben Sie Ihre Funktionen stattdessen folgendermaßen:
>>> def f(a=None):
... if a is None:
... a = [1,2,3]
Selbstverständlich können Sie anstelle von None eine Instanz eines beliebigen anderen immutablen Datentyps verwenden, ohne dass Seiteneffekte auftreten.