PEP 339 – Design des CPython-Compilers
- Autor:
- Brett Cannon <brett at python.org>
- Status:
- Zurückgezogen
- Typ:
- Informational
- Erstellt:
- 02. Feb. 2005
- Post-History:
Hinweis
Dieses PEP wurde zurückgezogen und in das Python Developer's Guide verschoben.
Zusammenfassung
Historisch (bis 2.4) umfasste die Kompilierung von Quellcode zu Bytecode zwei Schritte
- Parsen des Quellcodes in einen Parse Tree (Parser/pgen.c)
- Generieren von Bytecode basierend auf dem Parse Tree (Python/compile.c)
Historisch gesehen ist dies nicht die Funktionsweise eines Standardcompilers. Die üblichen Schritte für die Kompilierung sind:
- Parsen des Quellcodes in einen Parse Tree (Parser/pgen.c)
- Transformation des Parse Trees in einen Abstrakten Syntaxbaum (Python/ast.c)
- Transformation des AST in einen Kontrollflussgraphen (Python/compile.c)
- Generieren von Bytecode basierend auf dem Kontrollflussgraphen (Python/compile.c)
Ab Python 2.5 werden die oben genannten Schritte verwendet. Diese Änderung wurde vorgenommen, um die Kompilierung zu vereinfachen, indem sie in drei Schritte unterteilt wurde. Ziel dieses Dokuments ist es, darzulegen, wie die letzteren drei Schritte des Prozesses funktionieren.
Dieses Dokument befasst sich nicht damit, wie das Parsen funktioniert, außer dem, was für die Erklärung der Kompilierung notwendig ist. Es ist auch nicht erschöpfend in Bezug auf die Funktionsweise des gesamten Systems. Sie müssen höchstwahrscheinlich den Quellcode lesen, um ein genaues Verständnis aller Details zu erhalten.
Parse Trees
Pythons Parser ist ein LL(1)-Parser, der größtenteils auf der Implementierung basiert, die im Dragon Book beschrieben ist [Aho86].
Die Grammatikdatei für Python befindet sich in Grammar/Grammar, die numerischen Werte der Grammatikregeln sind in Include/graminit.h gespeichert. Die numerischen Werte für Token-Typen (literale Tokens wie :, Zahlen usw.) sind in Include/token.h enthalten. Der Parse Tree besteht aus node * Strukturen (wie in Include/node.h definiert).
Abfragen von Daten aus den Node-Strukturen können mit den folgenden Makros durchgeführt werden (die alle in Include/token.h definiert sind):
CHILD(node *, int)- Gibt das n-te Kind des Knotens zurück, wobei die Indizierung bei Null beginnt.
RCHILD(node *, int)- Gibt das n-te Kind des Knotens von rechts zurück; verwenden Sie negative Zahlen!
NCH(node *)- Anzahl der Kinder, die der Knoten hat.
STR(node *)- String-Repräsentation des Knotens; gibt z.B.
:für einen COLON-Token zurück.
TYPE(node *)- Der Typ des Knotens, wie in
Include/graminit.hangegeben.
REQ(node *, TYPE)- Stellt sicher, dass der Knoten den erwarteten Typ hat.
LINENO(node *)- Ruft die Zeilennummer des Quellcodes ab, die zur Erstellung der Parse-Regel geführt hat; definiert in Python/ast.c.
Um dieses Beispiel zu veranschaulichen, betrachten wir die Regel für 'while':
while_stmt: 'while' test ':' suite ['else' ':' suite]
Der Knoten, der dies repräsentiert, hat TYPE(node) == while_stmt und die Anzahl der Kinder kann 4 oder 7 sein, je nachdem, ob eine 'else'-Anweisung vorhanden ist. Um auf das erste ':' zuzugreifen und sicherzustellen, dass es sich tatsächlich um einen ':'-Token handelt, verwenden Sie (REQ(CHILD(node, 2), COLON).
Abstrakte Syntaxbäume (AST)
Der Abstrakte Syntaxbaum (AST) ist eine hochrangige Darstellung der Programmstruktur, ohne dass der Quellcode enthalten sein muss; er kann als abstrakte Darstellung des Quellcodes betrachtet werden. Die Spezifikation der AST-Knoten wird mit der Zephyr Abstract Syntax Definition Language (ASDL) [Wang97] spezifiziert.
Die Definition der AST-Knoten für Python befindet sich in der Datei Parser/Python.asdl.
Jeder AST-Knoten (der Anweisungen, Ausdrücke und verschiedene spezialisierte Typen wie Listen-Comprehensions und Ausnahmebehandler repräsentiert) wird durch ASDL definiert. Die meisten Definitionen im AST entsprechen einer bestimmten Quellcode-Konstruktion, wie z.B. einer 'if'-Anweisung oder einem Attribut-Lookup. Die Definition ist unabhängig von ihrer Realisierung in einer bestimmten Programmiersprache.
Das folgende Fragment des Python-ASDL-Konstrukts demonstriert den Ansatz und die Syntax.
module Python
{
stmt = FunctionDef(identifier name, arguments args, stmt* body,
expr* decorators)
| Return(expr? value) | Yield(expr value)
attributes (int lineno)
}
Das vorhergehende Beispiel beschreibt drei verschiedene Arten von Anweisungen: Funktionsdefinitionen, Return-Anweisungen und Yield-Anweisungen. Alle drei Arten gelten als vom Typ `stmt`, wie durch das '|' angezeigt, das die verschiedenen Arten trennt. Sie alle nehmen Argumente verschiedener Art und Mengen entgegen.
Modifikatoren des Argumenttyps geben die Anzahl der benötigten Werte an: '?' bedeutet optional, '*' bedeutet 0 oder mehr, keine Modifikation bedeutet nur ein einzelner Wert für das Argument und dieser ist erforderlich. `FunctionDef` nimmt zum Beispiel einen Bezeichner für den Namen, 'arguments' für `args`, null oder mehr `stmt`-Argumente für 'body' und null oder mehr `expr`-Argumente für 'decorators' entgegen.
Beachten Sie, dass etwas wie 'arguments', das ein Knotentyp ist, als einzelner AST-Knoten und nicht als eine Sequenz von Knoten wie bei `stmt` dargestellt wird, wie man es vielleicht erwarten würde.
Alle drei Arten haben auch ein 'attributes'-Argument; dies wird dadurch angezeigt, dass 'attributes' kein '|' davor hat.
Die obigen Anweisungsdefinitionen generieren den folgenden C-Strukturtyp.
typedef struct _stmt *stmt_ty;
struct _stmt {
enum { FunctionDef_kind=1, Return_kind=2, Yield_kind=3 } kind;
union {
struct {
identifier name;
arguments_ty args;
asdl_seq *body;
} FunctionDef;
struct {
expr_ty value;
} Return;
struct {
expr_ty value;
} Yield;
} v;
int lineno;
}
Es werden auch eine Reihe von Konstruktorfunktionen generiert, die (in diesem Fall) eine `stmt_ty`-Struktur mit der entsprechenden Initialisierung allokieren. Das Feld 'kind' gibt an, welche Komponente der Union initialisiert wird. Die Konstruktorfunktion `FunctionDef()` setzt 'kind' auf `FunctionDef_kind` und initialisiert die Felder 'name', 'args', 'body' und 'attributes'.
Speicherverwaltung
Bevor die eigentliche Implementierung des Compilers besprochen wird, ist eine Erörterung der Speicherverwaltung angebracht. Um die Speicherverwaltung zu vereinfachen, wird ein Arena-System verwendet. Das bedeutet, dass Speicher an einem einzigen Ort gebündelt wird, um die Allokation und Entfernung zu erleichtern. Dies ermöglicht die Eliminierung expliziter Speicherfreigaben. Da die Speicherzuweisung für allen benötigten Speicher im Compiler diesen mit der Arena registriert, ist ein einziger Aufruf zur Freigabe der Arena ausreichend, um den gesamten vom Compiler verwendeten Speicher freizugeben.
Im Allgemeinen, solange Sie nicht am kritischen Kern des Compilers arbeiten, kann die Speicherverwaltung vollständig ignoriert werden. Wenn Sie jedoch entweder ganz am Anfang oder ganz am Ende des Compilers arbeiten, müssen Sie sich mit der Funktionsweise der Arena befassen. Der gesamte Code, der sich auf die Arena bezieht, befindet sich entweder in Include/pyarena.h oder Python/pyarena.c.
PyArena_New() erstellt eine neue Arena. Die zurückgegebene PyArena-Struktur speichert Zeiger auf allen ihr zugewiesenen Speicher. Dies erledigt die Buchführung darüber, welcher Speicher freigegeben werden muss, wenn der Compiler den verwendeten Speicher nicht mehr benötigt. Diese Freigabe erfolgt mit PyArena_Free(). Dies muss nur an strategischen Stellen aufgerufen werden, an denen der Compiler beendet wird.
Wie oben erwähnt, sollten Sie sich im Allgemeinen keine Gedanken über die Speicherverwaltung machen, wenn Sie am Compiler arbeiten. Die technischen Details wurden so gestaltet, dass sie für die meisten Fälle verborgen bleiben.
Die einzige Ausnahme bildet die Verwaltung eines PyObject. Da der Rest von Python Referenzzählung verwendet, gibt es zusätzliche Unterstützung für die Arena, um jedes allokierte PyObject zu bereinigen. Diese Fälle sind sehr selten. Wenn Sie jedoch ein PyObject allokiert haben, müssen Sie die Arena durch Aufruf von PyArena_AddPyObject() darüber informieren.
Parse Tree zu AST
Der AST wird aus dem Parse Tree (siehe Python/ast.c) mit der Funktion PyAST_FromNode() generiert.
Die Funktion beginnt einen Baum-Walk des Parse Trees und erstellt dabei verschiedene AST-Knoten. Sie allokiert alle benötigten neuen Knoten, ruft die entsprechenden AST-Knoten-Erstellungsfunktionen für erforderliche Hilfsfunktionen auf und verbindet sie nach Bedarf.
Es ist zu beachten, dass es keine automatisierte oder symbolische Verbindung zwischen der Grammatikspezifikation und den Knoten im Parse Tree gibt. Es wird keine direkte Hilfe vom Parse Tree wie bei yacc bereitgestellt.
Man muss zum Beispiel verfolgen, mit welchem Knoten im Parse Tree man arbeitet (z.B. wenn man mit einer 'if'-Anweisung arbeitet, muss man nach dem ':'-Token Ausschau halten, um das Ende der Bedingung zu finden).
Die Funktionen, die aufgerufen werden, um AST-Knoten aus dem Parse Tree zu generieren, heißen alle `ast_for_xx`, wobei `xx` die Grammatikregel ist, die die Funktion behandelt (alias_for_import_name ist die Ausnahme). Diese rufen wiederum die Konstruktorfunktionen auf, wie sie durch die ASDL-Grammatik definiert und in Python/Python-ast.c (generiert von Parser/asdl_c.py) enthalten sind, um die Knoten des AST zu erstellen. Dies alles führt zu einer Sequenz von AST-Knoten, die in `asdl_seq`-Strukturen gespeichert sind.
Funktionen und Makros zum Erstellen und Verwenden von asdl_seq * Typen, wie sie in Python/asdl.c und Include/asdl.h zu finden sind.
asdl_seq_new()- Allokiert Speicher für ein `asdl_seq` für die angegebene Länge.
asdl_seq_GET()- Ruft das Element ab, das sich an einer bestimmten Position in einem `asdl_seq` befindet.
asdl_seq_SET()- Setzt einen bestimmten Index in einem `asdl_seq` auf den angegebenen Wert.
asdl_seq_LEN(asdl_seq *)- Gibt die Länge eines `asdl_seq` zurück.
Wenn Sie mit Anweisungen arbeiten, müssen Sie auch darauf achten, welche Zeilennummer die Anweisung generiert hat. Aktuell wird die Zeilennummer als letzter Parameter an jede `stmt_ty`-Funktion übergeben.
Kontrollflussgraphen
Ein Kontrollflussgraph (oft durch sein Akronym CFG bezeichnet) ist ein gerichteter Graph, der den Programmfluss modelliert, indem er grundlegende Blöcke verwendet, die die Zwischenrepräsentation (abgekürzt „IR“, und in diesem Fall Python-Bytecode) innerhalb der Blöcke enthalten. Grundlegende Blöcke selbst sind eine Gruppe von IR, die einen einzigen Einstiegspunkt, aber möglicherweise mehrere Ausstiegspunkte haben. Der einzelne Einstiegspunkt ist der Schlüssel zu grundlegenden Blöcken; es hat alles mit Sprüngen zu tun. Ein Einstiegspunkt ist das Ziel von etwas, das den Kontrollfluss ändert (wie ein Funktionsaufruf oder ein Sprung), während Ausstiegspunkte Anweisungen sind, die den Programmfluss ändern würden (wie Sprünge und 'return'-Anweisungen). Das bedeutet, dass ein grundlegender Block ein Code-Schnipsel ist, der am Einstiegspunkt beginnt und bis zu einem Ausstiegspunkt oder dem Ende des Blocks läuft.
Als Beispiel betrachten Sie eine 'if'-Anweisung mit einem 'else'-Block. Die Bedingung für das 'if' ist ein grundlegender Block, auf den der grundlegende Block zeigt, der den Code vor der 'if'-Anweisung enthält. Der 'if'-Anweisungsblock enthält Sprünge (die Ausstiegspunkte sind) zum wahren Körper des 'if' und zum 'else'-Körper (der NULL sein kann), die jeweils eigene grundlegende Blöcke sind. Beide Blöcke zeigen dann auf den grundlegenden Block, der den Code nach der gesamten 'if'-Anweisung repräsentiert.
CFGs sind normalerweise einen Schritt von der endgültigen Codeausgabe entfernt. Der Code wird direkt aus den grundlegenden Blöcken generiert (mit angepassten Sprungzielen basierend auf der Ausgabereihenfolge), indem eine Post-Order-Tiefensuche im CFG entlang der Kanten durchgeführt wird.
AST zu CFG zu Bytecode
Nachdem der AST erstellt wurde, ist der nächste Schritt die Erstellung des CFG. Der erste Schritt ist die Umwandlung des AST in Python-Bytecode, ohne dass Sprungziele auf spezifische Offsets aufgelöst werden (dies wird berechnet, wenn der CFG zum endgültigen Bytecode gelangt). Im Wesentlichen transformiert dies den AST in Python-Bytecode, wobei der Kontrollfluss durch die Kanten des CFG repräsentiert wird.
Die Umwandlung erfolgt in zwei Durchgängen. Der erste erstellt den Namensraum (Variablen können als lokal, frei/zell für Closures oder global klassifiziert werden). Danach wird der zweite Durchgang im Wesentlichen den CFG zu einer Liste abgeflacht und die Sprung-Offsets für die endgültige Bytecode-Ausgabe berechnet.
Der Umwandlungsprozess wird durch einen Aufruf der Funktion PyAST_Compile() in Python/compile.c initiiert. Diese Funktion übernimmt sowohl die Umwandlung des AST in einen CFG als auch die Ausgabe des endgültigen Bytecodes aus dem CFG. Der Schritt AST zu CFG wird hauptsächlich von zwei Funktionen gehandhabt, die von PyAST_Compile() aufgerufen werden: PySymtable_Build() und compiler_mod(). Die erstere befindet sich in Python/symtable.c, während die letztere in Python/compile.c liegt.
PySymtable_Build() beginnt mit dem Eintritt in den Startcodeblock für den AST (als Parameter übergeben) und ruft dann die entsprechende `symtable_visit_xx`-Funktion auf (wobei `xx` der AST-Knotentyp ist). Als Nächstes wird der AST-Baum durchlaufen, wobei die verschiedenen Codeblöcke, die die Reichweite einer lokalen Variable definieren, beim Eintritt und Austritt aus Blöcken mit `symtable_enter_block()` und `symtable_exit_block()` verarbeitet werden.
Sobald die Symboltabelle erstellt ist, ist es Zeit für die CFG-Erstellung, deren Code sich in Python/compile.c befindet. Dies wird von mehreren Funktionen gehandhabt, die die Aufgabe nach verschiedenen AST-Knotentypen aufteilen. Die Funktionen heißen alle `compiler_visit_xx`, wobei `xx` der Name des Knotentyps ist (z.B. `stmt`, `expr` usw.). Jede Funktion erhält einen struct compiler * und `xx_ty`, wobei `xx` der AST-Knotentyp ist. Typischerweise bestehen diese Funktionen aus einer großen 'switch'-Anweisung, die je nach Art des übergebenen Knotentyps verzweigt. Einfache Dinge werden inline in der 'switch'-Anweisung behandelt, während komplexere Transformationen an andere Funktionen namens `compiler_xx` ausgelagert werden, wobei `xx` ein beschreibender Name für das ist, was behandelt wird.
Beim Transformieren eines beliebigen AST-Knotens verwenden Sie das VISIT-Makro. Die entsprechende `compiler_visit_xx`-Funktion wird basierend auf dem Wert aufgerufen, der für <<Knotentyp> übergeben wird (also ruft VISIT(c, expr, node) compiler_visit_expr(c, node) auf). Das VISIT_SEQ-Makro ist sehr ähnlich, wird aber auf AST-Knotensequenzen aufgerufen (die Werte, die als Argumente für einen Knoten erstellt wurden, der den '*' -Modifikator verwendet hat). Es gibt auch VISIT_SLICE(), nur für die Behandlung von Slices.
Die Generierung von Bytecode wird von den folgenden Makros übernommen:
ADDOP()- fügt einen angegebenen Opcode hinzu.
ADDOP_I()- fügt einen Opcode hinzu, der ein Argument nimmt.
ADDOP_O(struct compiler *c, int op, PyObject *type, PyObject *obj)- fügt einen Opcode mit dem richtigen Argument basierend auf der Position des angegebenen PyObject in einem PyObject-Sequenzobjekt hinzu, aber ohne Handhabung von mangled names; wird verwendet, wenn Sie benannte Lookups von Objekten wie Globals, Konstanten oder Parametern durchführen müssen, bei denen Namens-Mangling nicht möglich ist und der Geltungsbereich des Namens bekannt ist.
ADDOP_NAME()- genau wie ADDOP_O, aber Namens-Mangling wird ebenfalls gehandhabt; wird für das Laden von Attributen oder das Importieren basierend auf Namen verwendet.
ADDOP_JABS()- erstellt einen absoluten Sprung zu einem grundlegenden Block.
ADDOP_JREL()- erstellt einen relativen Sprung zu einem grundlegenden Block.
Mehrere Hilfsfunktionen, die Bytecode emittieren und `compiler_xx()` genannt werden, wobei `xx` das ist, womit die Funktion hilft (list, boolop, etc.). Eine ziemlich nützliche ist `compiler_nameop()`. Diese Funktion sucht den Geltungsbereich einer Variablen und gibt basierend auf dem Ausdruckskontext den richtigen Opcode zum Laden, Speichern oder Löschen der Variablen aus.
Das Handling der Zeilennummer, auf der eine Anweisung definiert ist, wird von `compiler_visit_stmt()` übernommen und ist daher kein Problem.
Zusätzlich zur Bytecode-Generierung basierend auf dem AST-Knoten muss die Erstellung grundlegender Blöcke erfolgen. Nachfolgend sind die Makros und Funktionen zur Verwaltung grundlegender Blöcke aufgeführt.
NEW_BLOCK()- erstellt einen Block und setzt ihn als aktuell.
NEXT_BLOCK()- im Grunde NEW_BLOCK() plus Sprung vom aktuellen Block.
compiler_new_block()- erstellt einen Block, verwendet ihn aber nicht (wird zur Generierung von Sprüngen verwendet).
Nachdem der CFG erstellt wurde, muss er abgeflacht werden, und dann erfolgt die endgültige Bytecode-Ausgabe. Das Abflachen erfolgt mit einer Post-Order-Tiefensuche. Nach dem Abflachen werden die Sprung-Offsets basierend auf dem Abflachen zurückgepatcht und dann wird eine `PyCodeObject`-Datei erstellt. All dies wird durch den Aufruf von `assemble()` gehandhabt.
Einführung neuer Bytecodes
Manchmal erfordert ein neues Feature einen neuen Opcode. Aber das Hinzufügen von neuem Bytecode ist nicht so einfach wie die plötzliche Einführung von neuem Bytecode im AST -> Bytecode-Schritt des Compilers. Mehrere Codeabschnitte in ganz Python hängen von korrekten Informationen über den vorhandenen Bytecode ab.
Zuerst müssen Sie einen Namen und eine eindeutige Identifikationsnummer wählen. Die offizielle Liste der Bytecodes befindet sich in Include/opcode.h. Wenn der Opcode ein Argument aufnehmen soll, muss ihm eine eindeutige Nummer zugewiesen werden, die größer ist als die, die `HAVE_ARGUMENT` zugewiesen ist (wie in Include/opcode.h zu finden).
Sobald das Namens-/Nummernpaar ausgewählt und in Include/opcode.h eingetragen ist, müssen Sie es auch in Lib/opcode.py und Doc/library/dis.rst eintragen.
Mit einem neuen Bytecode müssen Sie auch die sogenannte Magic Number für .pyc-Dateien ändern. Die Variable `MAGIC` in Python/import.c enthält die Nummer. Das Ändern dieser Nummer führt dazu, dass alle .pyc-Dateien mit der alten MAGIC-Nummer vom Interpreter beim Import neu kompiliert werden.
Schließlich müssen Sie die Verwendung des neuen Bytecodes einführen. Die Änderung von Python/compile.c und Python/ceval.c sind die primären Orte, an denen Änderungen vorgenommen werden müssen. Aber Sie müssen auch das 'compiler'-Paket ändern. Die wichtigsten Dateien dafür sind Lib/compiler/pyassem.py und Lib/compiler/pycodegen.py.
Wenn Sie hier eine Änderung vornehmen, die die Ausgabe von bereits vorhandenem Bytecode beeinflussen kann und Sie die Magic Number nicht ständig ändern, stellen Sie sicher, dass Sie Ihre alten .py(c|o)-Dateien löschen! Auch wenn Sie am Ende die Magic Number ändern, wenn Sie den Bytecode ändern, ändern Sie während des Debuggens die Bytecode-Ausgabe, ohne die Magic Number ständig zu erhöhen. Das bedeutet, dass Sie veraltete .pyc-Dateien erhalten, die nicht neu erstellt werden. Wenn Sie find . -name '*.py[co]' -exec rm -f {} ';' ausführen, werden alle .pyc-Dateien gelöscht, was dazu führt, dass neue erstellt werden und Sie Ihre neuen Bytecodes richtig testen können.
Code-Objekte
Das Ergebnis von PyAST_Compile() ist ein PyCodeObject, das in Include/code.h definiert ist. Und damit haben Sie nun ausführbaren Python-Bytecode!
Die Code-Objekte (Bytecode) werden in Python/ceval.c ausgeführt. Diese Datei benötigt auch einen neuen `case`-Statement für den neuen Opcode in der großen `switch`-Anweisung in `PyEval_EvalFrameEx()`.
Wichtige Dateien
- Parser/
- Python.asdl
- ASDL-Syntaxdatei.
- asdl.py
- „Eine Implementierung der Zephyr Abstract Syntax Definition Language.“ Verwendet SPARK zum Parsen der ASDL-Dateien.
- asdl_c.py
- „Generiert C-Code aus einer ASDL-Beschreibung.“ Generiert Python/Python-ast.c und Include/Python-ast.h.
- spark.py
- SPARK Parser Generator SPARK.
- Python/
- Python-ast.c
- Erstellt C-Strukturen, die den ASDL-Typen entsprechen. Enthält auch Code zum Marshaling von AST-Knoten (Kern-ASDL-Typen haben Marshaling-Code in asdl.c). „Datei automatisch generiert von Parser/asdl_c.py“. Diese Datei muss nach jeder Grammatikänderung separat committet werden, da der `__version__`-Wert auf die neueste Revisionsnummer der Grammatikänderung gesetzt wird.
- asdl.c
- Enthält Code zur Handhabung des ASDL-Sequenztyps. Enthält auch Code zur Handhabung des Marshaling der Kern-ASDL-Typen wie Zahl und Bezeichner. Wird von Python-ast.c zum Marshaling von AST-Knoten verwendet.
- ast.c
- Wandelt Pythons Parse Tree in den Abstrakten Syntaxbaum um.
- ceval.c
- Führt Bytecode aus (auch bekannt als Eval-Schleife).
- compile.c
- Gibt Bytecode basierend auf dem AST aus.
- symtable.c
- Generiert eine Symboltabelle aus dem AST.
- pyarena.c
- Implementierung des Arena-Speichermanagers.
- import.c
- Heimat der Magic Number (genannt `MAGIC`) für die Bytecode-Versionierung.
- Include/
- Python-ast.h
- Enthält die tatsächlichen Definitionen der C-Strukturen, wie sie von Python/Python-ast.c generiert wurden. „Automatisch generiert von Parser/asdl_c.py“.
- asdl.h
- Header für die entsprechende Python/ast.c.
- ast.h
- Deklariert PyAST_FromNode() extern (aus Python/ast.c).
- code.h
- Headerdatei für Objects/codeobject.c; enthält die Definition von PyCodeObject.
- symtable.h
- Header für Python/symtable.c. Die Struktur `symtable` und `PySTEntryObject` sind hier definiert.
- pyarena.h
- Headerdatei für die entsprechende Python/pyarena.c.
- opcode.h
- Masterliste des Bytecodes; wenn diese Datei geändert wird, müssen mehrere andere Dateien entsprechend geändert werden (siehe „Einführung neuer Bytecodes“).
- Objects/
- codeobject.c
- Enthält Code im Zusammenhang mit PyCodeObject (ursprünglich in Python/compile.c).
- Lib/
- opcode.py
- Eine der Dateien, die geändert werden muss, wenn Include/opcode.h geändert wird.
- compiler/
- pyassem.py
- Eine der Dateien, die geändert werden muss, wenn Include/opcode.h geändert wird.
- pycodegen.py
- Eine der Dateien, die geändert werden muss, wenn Include/opcode.h geändert wird.
Referenzen
Compilers: Principles, Techniques, and Tools, http://www.amazon.com/exec/obidos/tg/detail/-/0201100886/104-0162389-6419108Quelle: https://github.com/python/peps/blob/main/peps/pep-0339.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT