Unser Programm könnte jetzt so aussehen:
import sys import grammar, word, symbol def deriveLeft(curWord, grammar): leftmostNonTerm = curWord.getLeftmostNonTerm() return grammar.deriveLeft(leftmostNonTerm, curWord) def generate(curWord, grammar, maxLen, seenWords={}): if seenWords.has_key(curWord): return else: seenWords[curWord] = 1 if len(curWord)>maxLen: return if curWord.isAllTerminal(): print curWord return for deriv in deriveLeft(curWord, grammar): generate(deriv, grammar, maxLen, seenWords) if __name__=="__main__": grammar = Grammar.Grammar(sys.argv[1]) generate(word.Word(str( grammar.getStartSymbol())), grammar, int(sys.argv[2]))
Anmerkungen:
Generate verwendet ein Default-Argument, um das seenWords-Dictionary beim Aufruf ohne Argumente (also dem nichtrekursiven Aufruf) korrekt zu initialisieren.
Default-Argumente dienen häufig zur Festlegung eines Standardfalls, etwa bei open: Normalerweise wird lesend geöffnet, wenn aber ein zweites Argument vorhanden ist, wird er zum Modus. Anderes Beispiel:
>>> def inc(var, amount=1): ... return var+amount ... >>> inc(3) 4 >>> inc(3,8) 11
Damit ist auch das eigenartige yieldRules-Argument aus der Grammatik-Klasse klar: Normal (d.h., wenn wir yieldRules nicht geben) gibt grammar.deriveLeft nur die Ergebnisse der Anwendung von Regeln zurück. Übergibt man aber ein “wahres” yieldRules, kommt eine Liste von Tupeln aus angewandter Regel und resultierendem Wort zurück.
Man kann Default-Argumente auch als keyword parameters verwenden:
>>> def incTuple(tup, am1=1, am2=1): ... return (tup[0]+am1, tup[1]+am2) ... >>> incTuple((1,1), am2=3) (2, 4)
Die Regeln für die Zuweisung von Aktualparametern (also dem, was im Funktionsaufruf steht) zu Formalparametern (also dem, was im Funktionskopf steht) sind etwa so:
Die wirklichen Regeln sind übrigens noch etwas komplizierter – im Zweifel gibt die Python Language Reference erschöpfende Auskunft. Aus diesen Regeln folgt, dass auch ohne Default-Parameter Keyword-Parameter verwendet werden können, um etwa ganz deutlich zu machen, welche Parameter welche Werte bekommen. Bei langen Argumentlisten kann sowas helfen, blöde Fehler zu vermeiden.
>>> def foo(bar, baz, bong): ... print bar, baz, bong ... >>> foo(bong=4, baz="baz", bar=9) 9 baz 4
Eine weitere Anwendung für Default-Argumente ist die Vermeidung von globalen Variablen bei Funktionen. In einer Hausaufgabe haben wir die Fibonacci-Zahlen rekursiv berechnet und wollten uns bereits berechnete Werte merken. Damals konnten wir das nur mit einer globalen Variable, was nicht gut ist. Die folgende Fassung vermeidet das:
def fib(n, valueCache={}): if valueCache.has_key(n): return valueCache[n] if n==1 or n==0: return 1 else: valueCache[n] = fib(n-1)+fib(n-2) return valueCache[n]
Das funktioniert, weil der Wert des Default-Arguments dann gesetzt wird, wenn die Funktion “compiliert” wird, also zu dem Zeitpunkt, zu dem der Interpreter die Zeile mit dem def verarbeitet. Bei jedem Aufruf von fib mit nur einem Argument hat danach valueCache im das gleiche Dictionary als Wert, eben das, in das wir all die Werte reingeschrieben haben. Dieser Trick wird uns später nochmal im Zusammenhang mit so genannten “Closures” begegnen.
Noch eine kurze Anmerkung zur Terminologie: Ich versuche, in diesem Skript meistens von Argumenten zu sprechen, wenn ich “Dinge, die an Funktionen übergeben werden” meine. Das Wort Parameter ist dazu weitgehend synonym (auch wenn manche Autoren da Unterschiede machen). Im Fall von Formal- und Aktualparametern ist allerdings die Verwendung des Wortes Argument sehr unüblich, weshalb ich hier doch Parameter sage. Der Grund dieser “selectional preference” ist wohl, dass einige einflussreiche Autoren versucht haben, Parameter als sozusagen syntaktische Kategorie zu fassen, während sie das Wort Argument für den konkreten Wert eines Parameters reservieren wollten. Letztlich ist das aber alles egal, die Leute reden in diesem Bereich, wie es ihnen gerade passt.
Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen
(1)
Nehmt an, ihr hättet ein großes Programm, das jede Menge Fehlermeldungen produzieren kann. Da nicht von vorneherein klar ist, wie und in welcher Sprache die Fehlermeldungen ausgegeben werden, soll eine eigene Funktion die Ausgabe der Fehler übernehmen. Dabei ist jedem Fehler eine Zahl zugeordnet (die dann wiederum besser in Klartextkonstanten definiert sein sollte: fileNotFoundError = 7 o.dgl.). Schreibt also eine Funktion reportError(errCode=-1, errTable=englishTable, fatal=0) -> None, die die Fehlermeldung zum errCode aus der Liste errTable nach stderr ausgibt und das Programm abbricht, wenn fatal wahr ist. Ist errCode==-1, soll das Programm etwas wie “No error specified” ausgeben.
(2)
Schreibt eine Klasse Set, die ein paar Mengenoperationen bereitstellt: Man soll Elemente hinzufügen und wegnehmen sowie auf Mitgliedschaft testen können, darüber hinaus wäre vielleicht noch die Berechnung noch Schnitt und Vereinigung zweier Mengen nett. Intern sollte die Menge wohl trotzdem als Dictionary dargestellt werden.