Python-Programmierung: Funktionen
Aus Wikibooks
Funktionen realisieren in Python das Konzept des Unterprogramms. Funktionen stellen, neben Klassen und Modulen, das wichtigste Werkzeug zur Gliederung eines Programms dar. Im Gegensatz zu Prozeduren, die Python nicht kennt, liefern Funktionen immer einen Wert zurück, der allerdings None sein kann.
Inhaltsverzeichnis |
[Bearbeiten] Funktionsobjekte
Funktionen sind, wie alles in Python, Objekte. Insbesondere sind sie Funktionen höherer Ordnung was bedeutet, dass sie als Argumente für Funktionen verwendet werden können oder umgangssprachlich »man mit ihnen rechnen kann«. In Python gibt es eine ganze Reihe unterschiedlicher Funktionstypen, die sich auch in der Standardtypenhierachie wiederfinden:
- Benutzerdefinierte Funktion - Sie werden mit den Statements
defoder als anonyme Funktion (namenlose Funktion) mit dem Statementlambdadefiniert. - Benutzerdefinierte Methode - Sind Funktionen, die an eine Klasse gebunden sind. Hierzu zählen die Instanz-, Klassen- und die statischen Methoden.
- Generatoren - Generatoren sind Funktionen, die einen Iterator liefern und jedem Aufruf der Iteratorfunktion
nexteinen neuen Wert liefern. Generatoren ist ein eigenes Kapitel gewidmet. - Klassen - Es mag überraschend klingen, dass Klassen auch als Funktion verwendet werden können, dabei wird diese Fähigkeit in Pythonprogrammen ständig verwendet - Es ist der Konstruktor, der in diesem Fall aufgerufen wird. Mehr dazu, findet sich im Artikel zu Klassen und Objekte.
[Bearbeiten] Funktionsdefinition
In den meisten Fällen werden Funktionen mit dem Statement def definiert. Die Syntax ist sehr einfach. Nach def folgt der Funktionsname, dem selbst, eingeschlossen in runden Klammern, die formale Parameterliste der Funktion folgen. Abgeschlossen wird die Funktionsdefiniton durch einen Doppelpunkt, mit dem der nachfolgende Funktionsblock eingeleitet wird.
>>> def Hallo(): ... print("Hallo!") >>> Hallo() Hallo! >>> def Hallo2(name): ... print("Hallo %s" % name) >>> Hallo("Welt") Hallo Welt
Eine Funktion wird aufgerufen, in dem an ihren Namen runde Klammern angefügt werden, in denen die Argumente stehen. Ohne Klammern liefert der Funktionsname, das Funktionsobjekt.
Formal passiert bei einer Funktionsdefinition folgendes: Das Statement def erzeugt aus dem Funktionskörper ein Codeobjekt welches in neu erzeugtes Funktionsobjekt aufgenommen wird. Das Funktionsobjekt wird, im Falle einer benutzerdefinierten Funktion bzw. Methode, an den angegebenen Namen gebunden und dieser in den aktuellen Namensraum eingetragen. Im Fall einer normalen Funktion ist dies der globale Namensraum des Moduls, im Fall einer Methode, der der Klasse. Es sind aber auch andere Fälle möglich, z.B. Closures.
[Bearbeiten] Funktionsattribute
Da Funktionen reguläre Objekte sind, besitzen sie auch Attribute. Es lassen sich sogar eigene Attribute setzen.[1]
>>> def EineFunktion(arg1, arg2, arg3): ... """ Ich bin der Docstring und diene der Dokumentation """ ... print(arg1, arg2, arg3) ... >>> EineFunktion(1, 2, 3) (1, 2, 3) >>> EineFunktion.__doc__ ' Ich bin der Docstring und diene der Dokumentation ' >>> EineFunktion.__name__ 'EineFunktion' >>> EineFunktion.func_dict {} >>> EineFunktion.einAttribut = "gruen" >>> EineFunktion.func_dict {'einAttribut': 'gruen'} >>> EineFunktion.einAttribut 'gruen'
Die Attribute __doc__, __name__ und func_dict werden automatisch vom Interpreter erzeugt. So enthält __doc__ den Docstring der Funktion. Eigene Attribute können, wie bei jedem anderen Objekt auch, mit dem Punkt-Operator gesetzt bzw gelesen werden, also z.B. EineFunktion.einAttribut = "gruen". In der Python-Referenz finden sich noch weitere vordefinierte Attribute.
Bleibt die Frage, wozu Funktionsattribute gut sein könnte. Hierzu sei ein Blick in das Standardpythonmodul doctest empfohlen. doctest sucht in einem Pythonmodul nach Zeichenketten, die wie eine interaktive Pythonsitzung aussehen, um Tests (Unittests) durchzuführen. Ein Ort, an dem doctest nach Tests sucht, sind Docstring, die nichts weiter sind, als Funktionsattribute.[2]
[Bearbeiten] Funktionsargumente
Funktionen können formale Parameter besitzen, die beim Aufruf mit Argumenten versorgt werden. Dies gilt für wohl alle Programmiersprachen, die das Konzept der Funktion kennen. Python geht aber über dieses Konzept hinaus, denn es kennt verschiedene Argumentarten. Schauen wir uns erst einmal die klassische Form der Argumentübergabe an, wie man sie auch aus anderen Programmiersprachen kennt:
[Bearbeiten] Positionale Argumente
>>> def Spielzeug(a, b, c): ... print "a=", a ... print "b=", b ... print "c=", c ... >>> Spielzeug(1, 2, 3) a= 1 b= 2 c= 3
Soweit nichts Überraschendes. Der Aufruf von Spielzeug verwendet sogenannte positionale Argumente. Der erste Aufrufwert wird dem ersten Argument (a) zugewiesen, der zweite dem zweiten (b) und der dritte dem dritten (c). Es geht aber auch anders:
[Bearbeiten] Schlüsselwort Argumente
>>> Spielzeug(1, c="Argument C", b="Argument b") a= 1 b= Argument b c= Argument C
In diesem Beispiel ist nur Argument 1 ein positionales Argument. Die nachfolgenden beiden sind jetzt sogenannte keyword arguments (Schlüsselwortargumente). Dabei werden die benannten Argumente des Funktionsaufrufs den entsprechenden formalen Parametern gleichen Namens zugeordnet. Positionale und Schlüsselwortargumente können gemeinsam verwendet werden, dabei müssen die positionalen immer vor den Schlüsselwortargumenten angegeben werden. Es gibt noch weitere Einschränkungen. Argumente dürfen weder doppelt, noch zuviel oder zu wenig (unter-) belegt werden:
>>> Spielzeug(1, c="Argument C", c="Argument b") SyntaxError: duplicate keyword argument >>> Spielzeug(c="Argument C", b="Argument b", 1) SyntaxError: non-keyword arg after keyword arg >>> Spielzeug(1, c="Argument C", b="Argument b", ex="Zuviel") Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Spielzeug() got an unexpected keyword argument 'ex' >>> Spielzeug(1, c="Argument C", b="Argument b", a="Zuviel") Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Spielzeug() got multiple values for keyword argument 'a' >>> Spielzeug(1, c="Argument C") Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Spielzeug() takes exactly 3 non-keyword arguments (1 given)
[Bearbeiten] Standardwerte (default values)
Beim Aufruf einer Funktion kann auf Argumente verzichtet werden, wenn die formalen Parameter der Funktion mit Standardwerten versehen ist:
>>> def TolleFunktion(a, b=True, c="Text", d=4711): ... print "a =", a ... print "b =", b ... print "c= ", c ... print "d= ", d ... >>> TolleFunktion(1) a = 1 b = True c= Text d= 4711 >>> TolleFunktion(d=8808) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: TolleFunktion() takes at least 1 non-keyword argument (0 given) >>> TolleFunktion(d=8808, a=7077) a = 7077 b = True c= Text d= 8808 >>> TolleFunktion(a="Text", b=None) a = Text b = None c= Text d= 4711
Selbstverständlich bleiben alle anderen Einschränkungen erhalten. Beim Aufruf TolleFunktion(d=8808) wurde für den formalem Parameter a kein Wert übergeben. Da für a aber kein Defaultwert angegeben wurde, wird ein Aufruffehler gemeldet.
Die Ausdrücke (statements), die die Defaultwerte erzeugen, werden zum Zeitpunkt der Definition der Funktion und nicht während ihrer Ausführung berechnet.
[Bearbeiten] Variable Argumentenlisten = *args und **kwargs
Die GUI-Elemente des Python Toolkits Tkinter verfügen über schier endlose Parameter, von denen man meist nur ein Bruchteil wirklich angeben muss. Um z.B. ein Label-Objekt zu erzeugen, reicht es meist aus, den Text festzulegen. Wenn es die Anwendung allerdings erfordert, lassen sich auch Schriftfarbe (Vorder- und Hintergrund), Ausrichtung, Umbruch, Font und vieles andere mehr bestimmen. Um nicht in völlig unübersichtlichen Objekterzeugungs- und Parametrisierungsaufrufen unter zu gehen, nutzt Tkinter sehr exzessive variable Schlüsselwortargumente.
>>> from Tkinter import * >>> l = Label(text="Hello World", font="Helvetica 24 bold", foreground="#b000e0", background="midnightBlue") >>> l.pack()
Für Funktionen, bei denen nicht feststeht mit welchen und wie vielen Argumenten sie aufgerufen werden, hält Python einen besonderen Mechanismus bereit, die Stern * und Doppelstern **-Notation. Eine Funktion Label des Beispiels, könnte z.B. folgendermaßen definiert sein:
>>> def Label(**kwargs): ... print "Schluesselwortargumente =", kwargs ... >>> Label(text="Hello World", font="Helvetica 24 bold", foreground="#b000e0", background="midnightBlue") Schluesselwortargumente = {'text': 'Hello World', 'font': 'Helvetica 24 bold', 'background': 'midnightBlue', 'foreground': '#b000e0'}
Die **-Notation sammelt alle überzähligen, d.h. nicht durch formale Parameter bereits abgedeckte Schlüsselwortargumente, ab und packt sie in ein assoziatives Array (dict). Mit Hilfe dieses Mechanismus können Pythonfunktionen mit beliebigen Argumenten aufgerufen werden, die nicht einmal zum Zeitpunkt der Funktionsdefinition bekannt sein müssen. Möchte man statt mit Schlüsselwort- mit positionalen Argumenten arbeiten, steht die *-Notation zur Verfügung. Bei ihr werden alle überzähligen Argumente in eine Liste in der Reihenfolge ihres Auftretens abgelegt.
Hier noch ein Beispiel, dass ein wenig mit den vielfältigen Möglichkeiten spielt:
>>> def SuperFunktion(a, b, c, *args, **kwargs): ... print "a =", a ... print "b =", b ... print "c =", c ... print "ARGS =", args ... print "KWARGS =", kwargs ... >>> SuperFunktion(1, 2, 3) a = 1 b = 2 c = 3 ARGS = () KWARGS = {} >>> SuperFunktion(c=1, b=2, a=3) a = 3 b = 2 c = 1 ARGS = () KWARGS = {} >>> SuperFunktion(c=1, b=2, a=3, f="XX", g="YY", h="ZZ") a = 3 b = 2 c = 1 ARGS = () KWARGS = {'h': 'ZZ', 'g': 'YY', 'f': 'XX'} >>> SuperFunktion("A", "B", "C", "D", "E", "F", xx="xx") a = A b = B c = C ARGS = ('D', 'E', 'F') KWARGS = {'xx': 'xx'} >>> SuperFunktion(1, 2, 3, "a", "b", "c", rot="#ff0000", gruen="#00ff00", blau="#0000ff") a = 1 b = 2 c = 3 ARGS = ('a', 'b', 'c') KWARGS = {'gruen': '#00ff00', 'blau': '#0000ff', 'rot': '#ff0000'}
[Bearbeiten] Rückgabewerte
Die Eigenschaft von Funktionen ist es, Werte an ihren Aufrufer zurück zu liefern. Sie tut dies immer, auch dann, wenn man explizit keinen Wert zurück liefert. Der Rückgabewert ist diesem Fall konsequenter Weise None
[Bearbeiten] Das return Statement
Das return Statement vereinigt zwei Eigenschaften in sich. Zum einen kann mit return ein Wert an den Aufrufer der Funktion zurückgeliefert werden, zum anderen wirkt es aber auch als Kontrollstruktur: return kann an einer beliebigen Stelle und auch mehrfach innerhalb einer Funktion auftauchen. Sobald der aktuelle Ausführungspfad das Statement erreicht, wird die Funktion beendet und zum Aufrufer zurück gekehrt.
return für sich allein stehen, dann wird None zurück geliefert, oder von einem Ausdruck gefolgt werden, der evaluiert und als Rückgabewert an den Aufrufer zurück geliefert wird.
>>> def add(a, b): ... return a+b ... >>> add(1, 2) 3 >>> ergebnis = add(1, 2) >>> ergebnis 3
[Bearbeiten] Generatoren und yield
Mit Python 2.2 wurde das Konzept des Generators als future Statement eingeführt und in Python 2.3 zu einem Standardstatement gemacht. Das Grundkonzept eines Generators ist sehr einfach und stringent; er erzeugt einen Iterator weswegen er korrekt itertor generator heißt. Generator spricht sich leichter.
Das Konzept des Iterators ist aus den itertools, von xrange, Methoden, wie iterkeys, und anderen Pythonelementen bekannt. Ein Iterator kann wie eine Liste oder ein Tupel verwendet werden. Statt über einer Liste zu iterieren, die natürlich auch Speicherplatz belegt, wird vom Iterator bei jedem Schritt ein Wert angefordert. Dies geschieht so lange, bis der Iterator eine StopIteration Exception wirft.
Ein iterator generator macht nichts anderes, als einen solchen Iterator zu erzeugen. Ein Python Funktion wird automatisch dann zu einem Generator, wenn sich in ihrem Rumpf ein yield Statement befindet. Dies klingt alles ein wenig abstrakt, weswegen ein Beispiel vielleicht etwas Licht in das Thema bringt und zeigt, wie elegant, einfach und leistungsfähig Generatoren sind.
>>> def countdown(): # Der Iterator Generator for i in range(3, 0, -1): yield i yield "BOOOMMMM!!!" >>> x = countdown() # x ist der Generator >>> for i in x: # automatisches Iterieren über den vom Generator erzeugten Iterator print i 3 2 1 BOOOMMMM!!! >>> x = countdown() >>> x.next() # next() ruft den nächsten Wert vom Iterator ab. 3 >>> x.next() 2 >>> x.next() 1 >>> x.next() 'BOOOMMMM!!!' >>> x.next() # wurde das Funktionsende erreicht wird eine StopIteration Exception ausgelöst Traceback (most recent call last): File "<pyshell#23>", line 1, in <module> x.next() StopIteration >>> type(countdown()) <type 'generator'> >>> type(countdown().next) <type 'method-wrapper'>
Was macht yield genau? Mit Aufruf der Funktion, also im Beispiel mit Aufruf von countdown, wird ein Generatorobjekt instanziert und zurückgeliefert. Dieses Generatorobjekt kann jetzt überall dort verwendet werden, wo Iteratoren erlaubt sind, also z.B. im for Statement. Das Python Iteratorprotokoll schreibt vor, dass ein Iterator über eine Methode next() verfügt, die bei jedem Aufruf den nächsten Wert der Iteration liefern muss. Natürlich nur, soweit noch Werte vorhanden sind. Im Falle von yield und dem iterator generator war Python so nett, alles nötige automatisch für uns zu erzeugen. Beim ersten Aufruf von next(), also im ersten Iterationsschritt, wird nun der Funktionskörper unserer Funktion (countdown()) betreten und alle Statements wie gewöhnlich abgearbeitet.
Bis zum yield. Dieses Statement verhält sich ähnlich wie return. Es wertet ein rechts vom ihm stehenden Ausdruck aus, verlässt die Funktion und liefert den Wert an den Aufrufer von next() zurück. Was yield von return unterscheidet ist, dass sich der Iterator den Zustand der Funktion merkt, d.h. alle Variablen, die Stelle (das yield), an der die Funktion verlassen wurde, werden bis zum nächsten Aufruf von next() eingefroren. Kommt es jetzt zum nächsten Iterationsschritt, also zum Aufruf von next() (explizit oder implizit in for), wird die Ausführung des Funktionsrumpfs genau an der Stelle fortgesetzt, an der sie vorher verlassen wurde, d.h. nach yield. Dieser Ablauf wiederholt sich so lange, bis die Funktion verlassen wird.
- ↑ Dies gilt primär für benutzerdefinierte Funktionen und Methoden. Eingebaute Funktionen können ein abweichendes Verhalten zeigen. Dies hängt sehr stark von der Pythonversion ab.
- ↑ Der Vollständigkeit halber sei erwähnt, dass auch Module und Klassen mit Docstrings ausgestattet werden können. Die sind, ganz konsequent, dann natürlich Objektattribute des jeweiligen Modul- bzw. Klassenobjekts. Python versucht sehr konsistent zu sein.