Parameterübergabe


Wertübergabe oder Referenzübergabe

Parameterübergabe als Staffellauf In den Programmiersprachen unterscheidet man im Wesentlichen zwischen zwei Formen der Parameterübergabe:

Anmerkung: Die Namensparameter (call by name), wie sie in ALGOL 60 und COBOL genutzt wurde, ist heute nicht mehr üblich.

und wie sieht es bei Python aus?

In vielen Büchern oder Einführungen in Python liest man, dass Python den einen oder den anderen Übergabemechanismus habe, also "call by reference" oder "call by value". Was ist nun richtig?
Humpty Dumpty Die Erklärung liefert uns Humpty Dumpty:
"Wenn ich ein Wort verwende", erwiderte Humpty Dumpty ziemlich geringschätzig, "dann bedeutet es genau, was ich es bedeuten lasse, und nichts anderes."
"Die Frage ist doch", sagte Alice, "ob du den Worten einfach so viele verschiedene Bedeutungen geben kannst".
"Die Frage ist", sagte Humpty Dumpty, "wer die Macht hat - und das ist alles. [...]"
Lewis Carroll, Through the Looking-Glass (Alles hinter den Spiegeln)

Die Autoren "dehnen" die ursprünglichen Begriffe, so dass sie passen.
Python benutzt einen Mechanismus, den man als "Call-by-Object" (auch "Call by Object Reference" oder "Call by Sharing") bezeichnet.

Paramterübergabe Beim Aufruf einer Funktion werden bei Python nicht Zeiger auf Variablen sondern Zeiger auf die zugrundeliegenden Objekte übergeben, insofern könnte man von einer Art Referenzübergabe sprechen.
Zur Erklärung betrachten wir das folgende Beispiel:
def ref_demo(x):
    print "x=",x," id=",id(x)
    x=42
    print "x=",x," id=",id(x)
Ruft man diese Funktion auf (siehe weiter unten) und überprüft gleichzeitig mittels der build-in-Funktion id() die Identität der Variable x, stellt man fest, dass das globale x so lange dem lokalen x der Funktion entspricht, bis man x in der Funktion einen anderen Wert zuweist. Paramterübergabe So verhält sich Python zuerst wie "call by reference", dann wie "call by value". Allerdings kann man beim Aufruf im Gegensatz zu einem echten "call by reference" beliebige Ausdrücke, wie bei der Wertübergabe übergeben.
>>> x=9
>>> ref_demo(x)
x= 9  id= 29488104
x= 42  id= 29489304
>>> id(x)
29488104
>>> 

Seiteneffekte

Von einem Funktionsaufruf erwartet man, dass die Funktion den korrekten Wert für die Argumente zurückliefert und sonst keine Effekte verursacht, z.B. Ändern von Speicherzuständen. Auch wenn manche Programmierer bewusst Seiteneffekte zur Lösung ihrer Probleme einsetzen, wird dies im Allgemeinen als schlechter Stil betrachtet.
Schlimmer ist es jedoch, wenn Seiteneffekte auftreten, mit denen man nicht gerechnet hat, und dadurch Fehler verursacht werden. Dies kann auch in Python passieren. Dies kann dann geschehen, wenn Listen oder Dictionaries als Parameter übergeben werden.
Im folgenden Beispiel wird die Liste fib, die als aktueller Parameter an die Funktion s() übergeben wird, mit der Liste [47,11] verkettet.
>>> def s(liste):
...     print id(liste)
...     liste += [47,11]
...     print id(liste)
... 
>>> fib = [0,1,1,2,3,5,8]
>>> id(fib)
24746248
>>> s(fib)
24746248
24746248
>>> id(fib)
24746248
>>> fib
[0, 1, 1, 2, 3, 5, 8, 47, 11]
Man kann dies verhindern, wenn man statt einer Liste eine Kopie der Liste als Parameter übergibt. Mittels der Slicing Funktion kann man ganz leicht eine Kopie erzeugen. So wird in s(fib[:]) nicht die Liste fib sonder eine komplette Kopie übergeben. Der Aufruf verändert also nicht den Wert von fib, wie man im folgenden interaktiven Programmstück sehen kann:
>>> def s(liste):
...     liste += [47,11]
...     print liste
... 
>>> fib = [0,1,1,2,3,5,8]
>>> s(fib[:])
[0, 1, 1, 2, 3, 5, 8, 47, 11]
>>> fib
[0, 1, 1, 2, 3, 5, 8]

Übergabe von Argumenten

Nicht nur Funktionen, sondern auch einem Python-Skript kann man Argumente übergeben. Ruft man ein Python-Skript aus einer Shell auf, werden die Argumten jeweils durch ein Leerzeichen voneinander getrennt hinter dem Skriptnamen aufgeführt. Im Python-Skript selber sind diese Argumente unter der Listenvaribale sys.argv abrufbar. Der Name des Skriptes (Dateiname) ist unter sys.argv[0] erreichbar.
Das Skript (argumente.py) listet sämtliche mitgegebenen Argumente in der Standardausgabe auf:
 # Modul sys wird importiert:
import sys                

# Iteration über sämtliche Argumente:
for eachArg in sys.argv:   
        print eachArg
Beispiel eines Aufrufes, falls das Skript unter argumente.py gespeichert worden ist:
python argumente.py python kurs fuer anfaenger
Dieser Aufruf erzeugt folgende Ausgabe
argumente.py
python
kurs
fuer
anfaenger

Variable Anzahl von Parametern

Wir führen nun Funktionen ein, die mit einer beliebige Anzahl von Argumenten aufgerufen werden können. Diejenigen mit Programmiererfahrungen in C und C++ kennen das Konzept als varargs. Ein Sternchen "*" wird in Python benutzt, um eine variable Anzahl von Parametern zu kennzeichen. Dazu wird das Sternchen unmittelbar vor einem Variablennamen gestellt.
>>> def varpafu(*x): print(x)
... 
>>> varpafu()
()
>>> varpafu(34,"Do you like Python?", "Of course")
(34, 'Do you like Python?', 'Of course')
>>> 
Aus dem vorigen Beispiel lernen wir, dass die Argumente, die bei einem Funktionsaufruf von varpafu übergeben werden, in einem Tupel gesammelt werden. Auf dieses Tupel kann als "normale" Variable im Rumpf (body) der Funktion zugegriffen werden. Ruft man die Funktion ohne jegliche Argumente auf, so ist x ein leeres Tupel.

Manchmal ist es notwendig, dass man eine feste Anzahl von Positionsparametern benötigt, die von einer beliebigen Anzahl von Parametern gefolgt werden können. Dies ist möglich, aber die Positionsparameter müssen immer zuerst kommen.

Im folgenden Beispiel benutzen wir einen Positionsparameter "city", der immer angegeben werden muss, gefolgt von einer beliebigen Anzahl von weiteren Städten "other_cities":
>>> def locations(city, *other_cities): print(city, other_cities)
... 
>>> locations("Berlin")
('Berlin', ())
>>> locations("Berlin","Freiburg","Stuttgart","Konstanz","Frankfurt")
('Berlin', ('Freiburg', 'Stuttgart', 'Konstanz', 'Frankfurt'))
>>> 

Übung

Schreibe eine Funktion, die das arithmetische Mittel aus einer variablen Anzahl von Werten berechnet.

Lösung

def arithmetic_mean(x, *l):
    """ The function calculates the arithmetic mean of a non-empty
        arbitrary number of numbers """
    sum = x
    for i in l:
        sum += i

    return sum / (1.0 + len(l))
Man mag sich fragen, warum wir sowohl einen Positionsparameter "x" und eine Paramater für eine variable Anzahl von Werten "*l" in unserer Funktionsdefinition benutzt haben. Die Idee besteht darin, dass wir erzwingen wollten, dass unsere Funktion immer mit einer nichtleeren Anzahl von Argumenten aufgerufen wird. Dies ist notwendig, um eine Division durch 0 zu vermeiden, was einen Fehler verursachen würde.

In der folgenden interaktiven Python-Sitzung, lernen wir, wie wir diese Funktion benutzen können. Dazu nehmen wir an, dass die Funktion arithmetic_mean in einer Datei mit dem Namen statistics.py gespeichert worden ist.
>>> from statistics import arithmetic_mean
>>> arithmetic_mean(4,7,9)
6.666666666666667
>>> arithmetic_mean(4,7,9,45,-3.7,99)
26.71666666666667
Das funktioniert gut, aber die Sache hat einen Haken. Was, wenn jemand die Funktion mit einer Liste statt mit einer variablen Zahl von Zahlen aufrufen will?

Im folgenden sehen wir, dass dann ein Fehler verursacht wird:
>>> l = [4,7,9,45,-3.7,99]
>>> arithmetic_mean(l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "statistics.py", line 8, in arithmetic_mean
    return sum / (1.0 + len(l))
TypeError: unsupported operand type(s) for /: 'list' and 'float'
Die Rettung besteht in der Benutzung eines weiteren Sternchens:
>>> arithmetic_mean(*l)
26.71666666666667
>>> 


* in Funktionsaufrufen

Ein Stern kann auch in einem Funktionsaufrauf erscheinen, wie wir in der vorigen Übung gesehen haben: Die Semantik ist in diesem Fall "invers" zu der Verwendung eines Sternes in der Funktionsdefinition. Ein Argument wird entpackt und nicht gepackt. In anderen Worten: Die Elemente einer Liste oder eines Tuples werden vereinzelt:
>>> def f(x,y,z):
...     print(x,y,z)
... 
>>> p = (47,11,12)
>>> f(*p)
(47, 11, 12)
Es besteht wohl kaum die Notwendigkeit zu erwähnen, dass diese Art unsere Funktion aufzurufen komfortabler als die folgende ist:
>>> f(p[0],p[1],p[2])
(47, 11, 12)
>>> 
Zusätzlich, dass dieser Aufruf weniger komfortabel ist, ist die vorige Aufrufsart im allgemeinen Fall nicht anwendbar, d.h. wenn Listen "unbekannter" Längen verwendet werden sollen. "Unbekannt" bedeutet, dass die Länge erst während der Laufzeit bekannt ist und nicht während man das Skript schreibt.

Beliebige Schlüsselwortparameter

Es gibt auch einen Mechanismus für eine beliebige Anzahl von Schlüsselwortparametern. Um dies zu ermöglichen wurde als Notation ein doppeltes Sternchen "**" eingeführt:
>>> def f(**args):
...     print(args)
... 
>>> f()
{}
>>> f(de="Germnan",en="English",fr="French")
{'fr': 'French', 'de': 'Germnan', 'en': 'English'}
>>> 

Doppeltes Sternchen im Funktionsaufruf

Das folgende Beispiel veranschaulicht die Verwendung von ** in einem Funktionsaufruf:
>>> def f(a,b,x,y):
...     print(a,b,x,y)
...
>>> d = {'a':'append', 'b':'block','x':'extract','y':'yes'}
>>> f(**d)
('append', 'block', 'extract', 'yes')