Eine Tarifregel aus dem Portal
Bei einem Stadtwerk tippt die Tarifabteilung ihre Preisregel selbst in ein Verwaltungsportal, etwa grundpreis + verbrauch * arbeitspreis. Die Regel ändert sich jedes Quartal.
Niemand will dafür das Abrechnungssystem neu kompilieren und neu starten. Das System behält stattdessen den Syntaxbaum der Regel und rechnet ihn für jede Rechnung Knoten für Knoten aus.
Am Ende dieser Lektion
Notiz
Ich kann einen Syntaxbaum direkt ausführen.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| Interpreter | ein Programm, das ein anderes ausführt, ohne Maschinencode zu erzeugen | |
| Knoten | ein Punkt in einem Baum | BinOp |
| Auswerten | den Wert eines Knotens ausrechnen | |
| Blatt | ein Knoten ohne Knoten darunter | Constant(value=7) |
| Umgebung | die erreichbaren Namen und ihre Werte | {'netto': 5} |
| Bindung | ein Name zusammen mit seinem Wert |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Python zeigt Ihnen seinen Baum
Python ist auch ein Compiler. Bevor es eine Zeile ausführt, zerlegt es sie in Tokens und baut daraus einen Syntaxbaum. Das Modul ast gibt diesen Baum für eine Zeile aus, die über eine Pipe kommt. --mode eval sagt: Die Zeile ist ein einzelner Ausdruck.
Jeder Knoten hat einen Namen. Ein Blatt hat nichts unter sich. Vergleichen Sie die zwei ersten Bäume.
Der Baum für 7 - 2 * 3
echo "7 - 2 * 3" | python3 -m ast --mode evalHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Der Baum für (7 - 2) * 3
echo "(7 - 2) * 3" | python3 -m ast --mode evalHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Ein Baum mit Namen
echo "netto * menge" | python3 -m ast --mode evalHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Beispiel: die Knoten, die Python ausgab
| Knoten | Was er ist | Woher sein Wert kommt |
|---|---|---|
| Constant(value=7) | Ein Blatt mit einer Zahl | Von der Zahl selbst |
| Name(id='netto') | Ein Blatt mit einem Namen | Vom Nachschlagen des Namens |
| BinOp(left, op, right) | Eine Rechnung mit zwei Seiten | Von beiden Seiten, dann vom op |
| Sub() oder Mult() | Welche Rechnung | Von nichts: Es ist eine Markierung |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Erst die Kinder, dann der Knoten
Ein baumdurchlaufender Interpreter ist eine Funktion, die einen Knoten auswertet. Ein Blatt mit einer Zahl liefert die Zahl. Eine Rechnung wertet zuerst ihr linkes und ihr rechtes Kind aus und rechnet dann mit den zwei Ergebnissen.
Ein Kind kann selbst ein ganzer Baum sein, deshalb ruft die Funktion sich selbst auf. Das Beispiel kann nur - und * mit Zahlen.
Ein Auswerter, der den Baum durchläuft
# Einen Syntaxbaum auswerten: eine Zahl oder [op, links, rechts].
def auswerten(baum):
if type(baum) == int:
return baum
op = baum[0]
# Erst beide Seiten ausrechnen: Das ist die Rekursion.
links = auswerten(baum[1])
rechts = auswerten(baum[2])
if op == "-":
return links - rechts
if op == "*":
return links * rechts
return None
baum = ["-", 7, ["*", 2, 3]]
print(auswerten(baum))
# Aendern Sie baum in ["*", ["-", 7, 2], 3] und fuehren Sie erneut aus.
Ein häufiger Fehler
Warnung
Viele glauben, ein Interpreter mache heimlich Maschinencode aus dem Baum und führe den aus. Ein baumdurchlaufender Interpreter tut das nie. Er besucht die Knoten und erledigt die Arbeit unterwegs, bei jedem Lauf von Neuem. Deshalb ist er langsamer als kompilierter Code und kann dafür sofort starten.
Namen stehen in einer Umgebung
Ein Blatt mit einem Namen braucht einen Wert. Der Interpreter führt dafür eine Umgebung: jeden erreichbaren Namen mit seinem Wert. Jedes solche Paar ist eine Bindung.
Pythons eval nimmt die Umgebung als Dictionary. Mit einem zweiten Dictionary sucht Python zuerst dort. So verdeckt ein innerer Bereich einen äußeren Namen.
Mit einer Umgebung auswerten
python3 -c "print(eval('netto * menge', {'netto': 5, 'menge': 3}))"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Einen Namen weglassen
python3 -c "print(eval('netto * menge', {'netto': 5}))"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Eine innere Umgebung zuerst durchsuchen
python3 -c "print(eval('netto * menge', {'netto': 5, 'menge': 3}, {'menge': 8}))"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Beispiel: netto * menge nachschlagen
| Befehl | netto gefunden in | menge gefunden in | Ausgabe |
|---|---|---|---|
| Erster | Dem einen Dictionary: 5 | Dem einen Dictionary: 3 | 15 |
| Zweiter | Dem einen Dictionary: 5 | Nirgends | NameError |
| Dritter | Dem äußeren Dictionary: 5 | Dem inneren Dictionary: 8 | 40 |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Weiterführend
Ergänzen Sie Ihren Auswerter um einen Knoten ["wenn", bedingung, dann, sonst]. Werten Sie die Bedingung aus. Ist sie nicht 0, werten Sie dann aus, sonst sonst.
Achten Sie darauf, dass immer nur einer der zwei Zweige ausgewertet wird. Schreiben Sie einen Test, der das beweist, mit einem Zweig, der sonst NameError werfen würde.