Anmerkungen zu Funktionsdefinitionen in Python
Begriffe
Funktionsdefinitionen werden umgangssprachlich auch manchmal ungenau als „Funktionen“ bezeichnet.
Eine Funktion ist aber eigentlich eine Entität des Laufzeitmodells (ein Funktionsobjekt), während eine Funktionsdefinition eine Entität des Quelltextmodells ist (eine Anweisung).
Bei der Ausführung einer Funktionsdefinition wird zur Laufzeit ein entsprechendes Funktionsobjekt angelegt und an den in der Funktionsdefinition festgelegten Namen gebunden.
Aufruf nicht-existenter Funktionen
Es ist kein Fehler, in einer Suite einen Namen zu verwenden, der zum Zeitpunkt der Definition gar nicht an ein Objekt gebunden ist, solange diese solch eine Bindung vor der ersten Auswertung jenes Namens erfolgt.
- Protokoll
def f():
g()def g():
print( 'g' )f()
g
Dies bedeutet aber auch, daß Tippfehler in Namen von Funktionen oder Variablen erst bei der Auswertung (also möglicherweise erst bei Kunden) gemeldet werden.
Einzeilige Funktionsdefinitionen
Es ist auch erlaubt, eine Anweisung in dieselbe Zeile hinter den Doppelpunkt zu schreiben. Auch mehrere Anweisung können hinter in derselben Zeile hinter dem Doppelpunkt stehen, die dann durch ein Semikolon »;« getrennt werden müssen. Falls mindestens eine Anweisung in derselben Zeile hinter dem Doppelpunkt steht, darf die Funktionsdefinition dann aber insgesamt nur eine Zeile umfassen.
- Protokoll
def LKW(): print( "Lastkraftwagen" )
LKW()
Lastkraftwagen
ℛ Stilregel Die mehrzeilige Funktionsdefinition, bei der die Suite in einer neuen Zeilen beginnt, gilt in Python als besserer Stil verglichen mit dem Schreiben der Suite in die Zeile, welche mit »def« beginnt.
Funktionen, die keine Änderungen hervorrufen
Wenn man eine Funktion schreiben will, die nichts macht, verwendet man die Anweisung »pass«.
- Protokoll
def NOP():
pass- Protokoll
NOP()
NOP()
- (keine Änderung)
Eine Auswertungsanweisung, die keine Änderung hervorruft, hat in einer Funktionsdefinition ein ähnliche Bedeutung wie »pass«, anders als bei direkter Eingabe wird der Wert einer Auswertungsanweisung nicht ausgegeben.
- Protokoll
def NOP():
0NOP()
- (keine Änderung)
Für bestimmte Fälle, wie etwa „leere“ Funktionen, sollte jedoch »pass« verwendet werden. Dies macht die Absicht des Autors besser verständlich, da sich die Verwendung von »pass« für solche Fälle eingebürgert hat.
Eigenarten der Python -Konsole
Beendigung von Suiten in der Python -Konsole
Das folgende Skript ist als Skript in einer Quelldatei vollkommen korrekt.
- Quelltext
def helloworld():
print( 'hello' )
helloworld()
Die Python-Konsole verlangt allerdings eine Leerzeile, bevor nach einer eingerückten Eingabe wieder eine Eingabe ohne Einrückung akzeptiert wird. In der folgenden Konsoleneingabe findet sich eine Leerzeile hinter »print( 'hello' )«.
- Konsoleneingabe
def helloworld():
print( 'hello' )
helloworld()
Das folgende Skript mit einer Leerzeile hinter »def helloworld():« ist als Skript in einer Quelldatei vollkommen korrekt.
- Quelltext
def helloworld():
print( 'hello' )
helloworld()
Die Python-Konsole verbietet allerdings Leerzeilen innerhalb eingerückter Einheiten.
- Konsoleneingabe
def helloworld():
print( 'hello' )
helloworld()
Größere Skripte sollten auch wegen dieser Eigenarten der Konsole besser in Dateien gehalten werden. Die Konsole ist für die Eingabe einzelner Zeilen oder notfalls auch kurzer Skripte geeignet.
Ausgaben in der Konsole
Werte von Auswertungsanweisungen werden in der Konsole ausgegeben. Diese Ausgabe erfolgt nicht mehr, wenn die Ausdrück in einer Funktion ausgewertet werden.
- Eine Auswertungsanweisung ist ein Ausdruck, der an einer Stelle verwendet wird, an der eine Anweisung erwartet wird.
- Protokoll
22 + 1
23
- Protokoll
def f():
22 + 1
f()- (keine Ausgabe)
Weder durch die Ausführung der def-Anweisung noch durch die Ausführung der Auswertungsanweisung »f()« wird der Name »_« an etwas gebunden.
- Protokoll
def f():
print( 22 + 1 )
f()23
Die Python -Konsole von IDLE
Wenn ein Skript im IDLE -Skriptfenster ausgeführt wurde, dann können die vom Skript erzeugten Bindungen danach in der IDLE -Konsole verwendet werden. So können im IDLE -Skriptfenster definierte Funktionen danach auch in der IDLE -Konsole aufgerufen werden (wenn die Bindungen noch gelten). Nach einer Änderung im Skript muß dieses aber zunächst neu gestartet werden, bevor sich die Änderungen auf die IDLE -Konsole auswirken können.
Zeichnen einer Flagge (Refaktor)
- Flagge ohne Unterprogramme
from turtle import *
W = 99
D = 2*Wpenup()
bgcolor( 0.7, 0.7, 0.7 )
left( 90 )
forward( W )
right( 90 )
backward( W*1.5 )
colormode( 255 )fillcolor( 0x00, 0x55, 0xA4 )
begin_fill()
forward( W ); right( 90 );
forward( D ); right( 90 );
forward( W ); right( 90 );
forward( D ); right( 90 );
end_fill()
forward( W );fillcolor( 0xFF, 0xFF, 0xFF )
begin_fill()
forward( W ); right( 90 );
forward( D ); right( 90 );
forward( W ); right( 90 );
forward( D ); right( 90 );
end_fill()
forward( W );fillcolor( 0xEF, 0x54, 0x31 )
begin_fill()
forward( W ); right( 90 );
forward( D ); right( 90 );
forward( W ); right( 90 );
forward( D ); right( 90 );
end_fill()
forward( W );hideturtle()
- Flagge mit Unterprogrammen und Variablen
from turtle import *
W = 99
D = 2*Wdef rect():
begin_fill()
forward( W ); right( 90 );
forward( D ); right( 90 );
forward( W ); right( 90 );
forward( D ); right( 90 );
end_fill()
forward( W )penup()
bgcolor( 0.7, 0.7, 0.7 )
left( 90 )
forward( W )
right( 90 )
backward( W*1.5 )
colormode( 255 )fillcolor( 0x00, 0x55, 0xA4 ); rect()
fillcolor( 0xFF, 0xFF, 0xFF ); rect()
fillcolor( 0xEF, 0x54, 0x31 ); rect()hideturtle()
Namen von Funktoren (Stil)
Ein Funktor sollte einen nicht zu langen aussagekräftigen Namen haben. Der Name des Funktors sollte im besten Fall schon alles über den Funktor sagen.
Es ist einfacher, einen guten Namen für einen Funktor zu finden wenn diese „nur eine Sache“ macht. Dann sagt man auch, der Funktor habe eine hohe Kohäsion.
Ein „und“ im Namen eines Funktors könnte ein Indiz dafür sein, daß der Funktor „zwei Sachen“ macht. Dann könnte es richtig sein, den Funktor in zwei Funktoren zu zerlegen.
Ein Funktor, dessen Aufruf hauptsächlich für einen bestimmten Wert steht, sollte nach diesem Wert benannt werden. Der Name solch eines Funktors ist daher oft ein Substantiv oder eine Nominalphrase. Man findet hier oft auch abgekürzte Nominalphrasen: »len« („Länge“), »randint« („Zufallszahl“), »max« („Maximum“).
Ein Funktor, dessen Aufruf hauptsächlich eine bestimmte Aktivität bewirkt, sollte nach dieser Aktivität benannt werden. Der Name solch eines Funktors ist daher oft ein Verb oder eine Verbalphrase wie zum Beispiel »print« („drucke“).
Regel für Übungsaufgaben
Zur Lösung von Übungsaufgaben dürfen ab jetzt Funktionen („Hilfsfunktionen“) definiert und verwendet werden, auch wenn dies in der Aufgabenstellung nicht verlangt wird. (Die Definition dieser Funktionen wird damit auch ein Teil der Lösung und sollte dann beim Einreichen einer Lösung mit abgegeben werden.)
Zur Definition einer benannten Funktion soll ab jetzt »def« verwendet werden und keine Lambda-Bindung. (Lambda-Funktionen sind hauptsächlich für die Verwendung als Argumente gedacht, die Bindung eines Namens an eine Lambda-Funktion gilt als schlechter Stil.)
- Lambda-Bindung Bindung einer Lambda-Funktion an einen Namen
- Lambda-Funktion Wert eines Lamba-Ausdrucks
- Lambda-Ausdruck mit »lambda« beginnender Ausdruck
Übungsaufgaben
/ Schildkröten
Schreiben Sie eine def-Funktionsdefinition für eine Funktion, die erst »pendown()« auswertet und dann ein kleines gleichseitiges Dreieck zeichnet.
Bewegen Sie die Schildkröte dann im Hauptprogramm nach einer Auswertung von »penup()« an verschiedene Stellen und rufen Sie die zuvor geschriebene Funktion auf, um an der jeweiligen Stelle ein gleichseitiges Dreieck zeichnen zu lassen.
“The computer gives us words that do doug things. What daddy does is make new words to make computers easier to use."
The Mental Game of Python - Raymond Hettinger (PyBay 2019), 21′
- “doug things” = “nerd things”? (In the online text based social medium “United Heroes MUSH”, Douglas "Doug" Ramsey is a nerd. “Doug was always a smarty, a bit of a nerd, good with computers and fond of science fiction and fantasy novels and role-playing games.”, “What Doug's power does, is actually rapid mental decryption.”)
/ Schildkröten (1) ⃗
Schreiben Sie eine def-Funktionsdefinition für eine Funktion, die ein kleines gleichseitiges Viereck zeichnet.
Bewegen Sie die Schildkröte dann an verschiedene Stellen und rufen Sie diese Funktion auf, um an der jeweiligen Stelle ein gleichseitiges Viereck zeichnen zu lassen.
/ Schildkröten (2) *
Schreiben Sie eine Funktion, welche die Schildkröte an eine zufällige Stellen bewegt und dann jeweils ein ihr als Argument übergebenes Objekt aufruft.
Rufen Sie diese Funktion dann jeweils einmal mit den folgenden Argumentwerten auf: 1.) eine Funktion, die ein kleines Dreieck zeichnet, 2.) eine Funktion, die ein kleines Rechteck zeichnet.
/ Verschachtelungen von Definitionen
main.py
def f():
print( 'f' )def g():
f()g()
- Protokoll
f
In dem obigen Programm wird die Funktion »f« nur in der Definition von »g« aufgerufen.
In solch einem Fall kann die Definition von »f« auch direkt vor dem Aufruf in der Definition von »g« erfolgen!
Setzen Sie die Definition von »f« daher zu Übungszwecken einmal in die Definition von »g« ein!
Hierzu muß der Anfang der Definition von »f« soweit eingerückt sein, wie alle Anweisungen in der Suite von »g«, während der print-Aufruf weiterhin noch weiter eingerückt sein muß als der Beginn der Definition von »f«.
(Diese Verschachtelung von Funktionen sollte in der Praxis aber nur verwendet werden, wenn es einen besonderen Grund dafür gibt. Sonst sollte sie eher vermieden werden. Sie erfolgt hier nur, um das Schreiben von Verschachtelungen, das auch in weiteren Fällen hilfreich sein kann, zu üben.)
Der Typ von def-Funktion ⃗
Wir betrachten erneut die folgende Funktionsdefinition:
- Protokoll
def LKW():
print( "Lastkraftwagen" )
Unsere Funktion hat den Typ »function«.
- Protokoll
type( LKW )
<class 'function'>
Umbenennen von def-Funktionen ⃗
- Protokoll
def LKW():
print( "Last", end = "" )
print( "kraftwagen" )LKW()
Lastkraftwagen
Wir zeigen im folgenden die Umbenennung der definierten Funktion von »LKW« in »Lakrawa«.
- Protokoll
Lakrawa = LKW
Lakrawa()
Lastkraftwagen
- Protokoll
del LKW
Lakrawa()
Lastkraftwagen
- Protokoll
LKW()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'LKW' is not defined- Protokoll
def LKW():
print( "Last", end = "" );
print( "kraftwagen" )
File "<stdin>", line 3
print( "kraftwagen" )
^
IndentationError: unindent does not match any outer indentation level
- Protokoll
def LKW():
print( "Last", end = "" );
print( "kraftwagen" )
File "<stdin>", line 3
print( "kraftwagen" )
^
IndentationError: unexpected indent