Können Sie neue Anweisungen (wie hinzufügen print
, raise
, with
) zu Pythons Syntax?
Sagen Sie, um zu erlauben ..
mystatement "Something"
Oder,
new_if True:
print "example"
Nicht so sehr, wenn Sie sollten , sondern wenn es möglich ist (ohne den Python-Interpreter-Code zu ändern)
Antworten:
Dies kann nützlich sein - Python-Interna: Hinzufügen einer neuen Anweisung zu Python , hier zitiert:
Dieser Artikel ist ein Versuch, die Funktionsweise des Frontends von Python besser zu verstehen. Das bloße Lesen von Dokumentation und Quellcode kann etwas langweilig sein, daher gehe ich hier praktisch vor: Ich werde eine hinzufügen
until
Python Anweisung .Die gesamte Codierung für diesen Artikel wurde für den neuesten Py3k-Zweig im Python Mercurial-Repository-Spiegel durchgeführt .
Das
until
AussageEinige Sprachen, wie Ruby, haben eine
until
Aussage, die die Ergänzung zuwhile
(until num == 0
entsprichtwhile num != 0
) ist. In Ruby kann ich schreiben:Und es wird gedruckt:
Daher möchte ich Python eine ähnliche Funktion hinzufügen. Das heißt, schreiben zu können:
Ein Exkurs zur Befürwortung der Sprache
In diesem Artikel wird nicht versucht
until
, Python eine Anweisung hinzuzufügen. Obwohl ich denke, dass eine solche Aussage den Code klarer machen würde und dieser Artikel zeigt, wie einfach das Hinzufügen ist, respektiere ich Pythons Philosophie des Minimalismus voll und ganz. Alles, was ich hier wirklich versuchen möchte, ist einen Einblick in das Innenleben von Python zu gewinnen.Ändern der Grammatik
Python verwendet einen benutzerdefinierten Parser-Generator mit dem Namen
pgen
. Dies ist ein LL (1) -Parser, der Python-Quellcode in einen Analysebaum konvertiert. Die Eingabe in den Parsergenerator ist die DateiGrammar/Grammar
[1] . Dies ist eine einfache Textdatei, die die Grammatik von Python angibt.[1] : Von nun an werden Verweise auf Dateien in der Python-Quelle relativ zum Stammverzeichnis des Quellbaums angegeben. Dies ist das Verzeichnis, in dem Sie configure und make zum Erstellen von Python ausführen.
An der Grammatikdatei müssen zwei Änderungen vorgenommen werden. Die erste besteht darin, eine Definition für die
until
Anweisung hinzuzufügen . Ich fand heraus, wo diewhile
Anweisung definiert wurde (while_stmt
) und fügteuntil_stmt
unten hinzu [2] :[2] : Dies zeigt eine gängige Technik, die ich beim Ändern von Quellcode verwende, mit dem ich nicht vertraut bin: Arbeiten nach Ähnlichkeit . Dieses Prinzip wird nicht alle Ihre Probleme lösen, aber es kann den Prozess definitiv vereinfachen. Denn alles, wofür getan werden
while
muss, muss auch getan werdenuntil
, dient es als ziemlich gute Richtlinie.Beachten Sie, dass ich beschlossen habe, die
else
Klausel von meiner Definition von auszuschließenuntil
, nur um sie ein wenig anders zu machen (und weil ich ehrlich gesagt die nicht magelse
Klausel der Schleifen und nicht denke, dass sie gut zum Zen von Python passt).Die zweite Änderung ist die Regel zu ändern , um
compound_stmt
zu schließenuntil_stmt
, wie Sie im Snippet oben sehen können. Es ist gleichwhile_stmt
wieder da.Wenn Sie laufen
make
nach der ÄnderungGrammar/Grammar
, Ankündigung , dass daspgen
Programm auszuführen ist neu zu generierenInclude/graminit.h
undPython/graminit.c
, und dann mehr Dateien bekommen wieder zusammengestellt.Ändern des AST-Generierungscodes
Nachdem der Python-Parser einen Analysebaum erstellt hat, wird dieser Baum in einen AST konvertiert, da ASTs in nachfolgenden Phasen des Kompilierungsprozesses viel einfacher zu bearbeiten sind.
Wir werden also einen Besuch abstatten,
Parser/Python.asdl
der die Struktur der ASTs von Python definiert, und einen AST-Knoten für unsere neueuntil
Anweisung hinzufügen , ebenfalls direkt unterwhile
:Wenn Sie jetzt ausführen
make
, beachten Sie , dass vor dem Kompilieren einer Reihe von DateienParser/asdl_c.py
C-Code aus der AST-Definitionsdatei generiert wird. DiesGrammar/Grammar
ist ein weiteres Beispiel für den Python-Quellcode, der eine Minisprache (mit anderen Worten DSL) verwendet, um die Programmierung zu vereinfachen. DaParser/asdl_c.py
es sich um ein Python-Skript handelt, handelt es sich um eine Art Bootstrapping. Um Python von Grund auf neu zu erstellen, muss Python bereits verfügbar sein.Während
Parser/asdl_c.py
der Code zum Verwalten unseres neu definierten AST-Knotens (in die DateienInclude/Python-ast.h
undPython/Python-ast.c
) generiert wurde , müssen wir den Code, der einen relevanten Analysebaumknoten konvertiert, von Hand schreiben. Dies erfolgt in der DateiPython/ast.c
. Dortast_for_stmt
konvertiert eine Funktion mit dem Namen Analysebaumknoten für Anweisungen in AST-Knoten. Wiederumwhile
springen wir , geleitet von unserem alten Freund , direkt in die große Richtung,switch
um zusammengesetzte Anweisungen zu verarbeiten, und fügen eine Klausel hinzu füruntil_stmt
:Jetzt sollten wir implementieren
ast_for_until_stmt
. Hier ist es:Auch dies wurde codiert, während das Äquivalent genau betrachtet wurde
ast_for_while_stmt
, mit dem Unterschied, dassuntil
ich mich entschieden habe, dieelse
Klausel nicht zu unterstützen . Wie erwartet wird der AST rekursiv erstellt, wobei andere AST-Erstellungsfunktionen wieast_for_expr
der Bedingungsausdruck undast_for_suite
der Hauptteil deruntil
Anweisung verwendet werden. Schließlich wird ein neuer Knoten mit dem NamenUntil
zurückgegeben.Beachten Sie, dass wir
n
mit einigen Makros wieNCH
und auf den Analysebaumknoten zugreifenCHILD
. Diese sind verständlich - ihr Code ist inInclude/node.h
.Exkurs: AST-Zusammensetzung
Ich habe mich entschieden, einen neuen AST-Typ für die
until
Anweisung zu erstellen , aber eigentlich ist dies nicht erforderlich. Ich hätte etwas Arbeit sparen und die neue Funktionalität mithilfe der Zusammensetzung vorhandener AST-Knoten implementieren können, da:Ist funktional äquivalent zu:
Anstatt den
Until
Knoten in zu erstellenast_for_until_stmt
, hätte ich als Kind einenNot
Knoten mit einemWhile
Knoten erstellen können . Da der AST-Compiler bereits weiß, wie mit diesen Knoten umzugehen ist, können die nächsten Schritte des Prozesses übersprungen werden.Kompilieren von ASTs in Bytecode
Der nächste Schritt ist das Kompilieren des AST in Python-Bytecode. Die Kompilierung hat ein Zwischenergebnis, das ein CFG (Control Flow Graph) ist, aber da derselbe Code es verarbeitet, werde ich dieses Detail vorerst ignorieren und es für einen anderen Artikel belassen.
Der Code, den wir uns als nächstes ansehen werden, ist
Python/compile.c
. In Anlehnung anwhile
finden wir die Funktioncompiler_visit_stmt
, die für das Kompilieren von Anweisungen in Bytecode verantwortlich ist. Wir fügen eine Klausel hinzu fürUntil
:Wenn Sie sich fragen, was
Until_kind
ist, ist es eine Konstante (eigentlich ein Wert der_stmt_kind
Aufzählung), die automatisch aus der AST-Definitionsdatei in generiert wirdInclude/Python-ast.h
. Wie auch immer, wir nennencompiler_until
das natürlich noch nicht. Ich werde gleich darauf zurückkommen.Wenn Sie neugierig sind wie ich, werden Sie feststellen, dass
compiler_visit_stmt
das eigenartig ist. Keine Menge vongrep
-ping im Quellbaum zeigt, wo er aufgerufen wird. In diesem Fall bleibt nur eine Option übrig - C-Makro-Fu. In der Tat führt uns eine kurze Untersuchung zu demVISIT
Makro, das definiert ist inPython/compile.c
:Es wird verwendet , aufzurufen
compiler_visit_stmt
incompiler_body
. Zurück zu unserem Geschäft jedoch ...Wie versprochen, hier ist
compiler_until
:Ich muss ein Geständnis machen: Dieser Code wurde nicht basierend auf einem tiefen Verständnis des Python-Bytecodes geschrieben. Wie der Rest des Artikels wurde es in Nachahmung der Verwandtschaftsfunktion durchgeführt
compiler_while
. Beachten Sie jedoch, dass die Python-VM stapelbasiert ist, und werfen Sie einen Blick in die Dokumentation desdis
Moduls, das eine Liste der Python-Bytecodes enthält mit Beschreibungen enthält, können Sie verstehen, was vor sich geht.Das war's, wir sind fertig ... oder?
Nachdem wir alle Änderungen vorgenommen und ausgeführt haben
make
, können wir den neu kompilierten Python ausführen und unsere neueuntil
Anweisung ausprobieren :Voila, es funktioniert! Sehen wir uns den Bytecode an, der für die neue Anweisung mithilfe des
dis
Moduls wie folgt erstellt wurde:Hier ist das Ergebnis:
Die interessanteste Operation ist Nummer 12: Wenn die Bedingung erfüllt ist, springen wir nach der Schleife zu. Dies ist die richtige Semantik für
until
. Wenn der Sprung nicht ausgeführt wird, läuft der Schleifenkörper weiter, bis er zu der Bedingung bei Operation 35 zurückspringt.Ich fühlte mich gut mit meiner Änderung und versuchte dann, die Funktion auszuführen (auszuführen
myfoo(3)
), anstatt ihren Bytecode anzuzeigen. Das Ergebnis war weniger als ermutigend:Whoa ... das kann nicht gut sein. Also, was ist schief gelaufen?
Der Fall der fehlenden Symboltabelle
Einer der Schritte, die der Python-Compiler beim Kompilieren des AST ausführt, ist das Erstellen einer Symboltabelle für den von ihm kompilierten Code. Der Aufruf von
PySymtable_Build
inPyAST_Compile
ruft das Symboltabellenmodul (Python/symtable.c
) auf, das den AST auf ähnliche Weise wie die Codegenerierungsfunktionen durchläuft. Eine Symboltabelle für jeden Bereich hilft dem Compiler dabei, einige wichtige Informationen herauszufinden, z. B. welche Variablen global und welche lokal für einen Bereich sind.Um das Problem zu beheben, müssen wir die
symtable_visit_stmt
Funktion in ändernPython/symtable.c
und Code für die Behandlung vonuntil
Anweisungen nach dem ähnlichen Code fürwhile
Anweisungen hinzufügen [3] :[3] : Übrigens, ohne diesen Code gibt es eine Compiler-Warnung für
Python/symtable.c
. Der Compiler stellt fest, dass derUntil_kind
Aufzählungswert in der switch-Anweisung von nicht behandelt wird,symtable_visit_stmt
und beschwert sich. Es ist immer wichtig, nach Compiler-Warnungen zu suchen!Und jetzt sind wir wirklich fertig. Durch das Kompilieren der Quelle nach dieser Änderung wird die Ausführung der
myfoo(3)
Arbeit wie erwartet ausgeführt.Fazit
In diesem Artikel habe ich gezeigt, wie Sie Python eine neue Anweisung hinzufügen. Obwohl der Code des Python-Compilers einiges an Basteln erfordert, war die Änderung nicht schwer zu implementieren, da ich eine ähnliche und vorhandene Anweisung als Richtlinie verwendet habe.
Der Python-Compiler ist ein hochentwickelter Teil der Software, und ich behaupte nicht, ein Experte darin zu sein. Ich interessiere mich jedoch sehr für die Interna von Python und insbesondere für das Front-End. Daher fand ich diese Übung eine sehr nützliche Ergänzung zur theoretischen Untersuchung der Prinzipien und des Quellcodes des Compilers. Es wird als Basis für zukünftige Artikel dienen, die tiefer in den Compiler eindringen.
Verweise
Ich habe einige ausgezeichnete Referenzen für die Konstruktion dieses Artikels verwendet. Hier sind sie in keiner bestimmten Reihenfolge:
Originalquelle
quelle
until
istisa
/isan
wie inif something isa dict:
oderif something isan int:
Eine Möglichkeit, solche Dinge zu tun, besteht darin, die Quelle vorzuverarbeiten und zu ändern und Ihre hinzugefügte Anweisung in Python zu übersetzen. Es gibt verschiedene Probleme, die dieser Ansatz mit sich bringt, und ich würde ihn nicht für den allgemeinen Gebrauch empfehlen, aber für das Experimentieren mit Sprache oder für die Metaprogrammierung für bestimmte Zwecke kann er gelegentlich nützlich sein.
Nehmen wir zum Beispiel an, wir möchten eine "myprint" -Anweisung einführen, die anstelle des Druckens auf dem Bildschirm in einer bestimmten Datei protokolliert. dh:
wäre gleichbedeutend mit
Es gibt verschiedene Möglichkeiten, wie Sie das Ersetzen durchführen können, von der Regex-Ersetzung über das Generieren eines AST bis hin zum Schreiben eines eigenen Parsers, je nachdem, wie genau Ihre Syntax mit dem vorhandenen Python übereinstimmt. Ein guter Zwischenansatz ist die Verwendung des Tokenizer-Moduls. Dies sollte es Ihnen ermöglichen, neue Schlüsselwörter, Kontrollstrukturen usw. hinzuzufügen, während Sie die Quelle ähnlich wie den Python-Interpreter interpretieren, um den Bruch zu vermeiden, den rohe Regex-Lösungen verursachen würden. Für den obigen "myprint" können Sie den folgenden Transformationscode schreiben:
(Dies macht myprint effektiv zu einem Schlüsselwort, sodass die Verwendung als Variable an anderer Stelle wahrscheinlich Probleme verursacht.)
Das Problem ist dann, wie man es benutzt, damit Ihr Code von Python aus verwendet werden kann. Eine Möglichkeit besteht darin, eine eigene Importfunktion zu schreiben und damit Code zu laden, der in Ihrer benutzerdefinierten Sprache geschrieben wurde. dh:
Dies erfordert jedoch, dass Sie Ihren benutzerdefinierten Code anders als normale Python-Module behandeln. dh "
some_mod = myimport("some_mod.py")
" statt "import some_mod
"Eine andere ziemlich nette (wenn auch hackige) Lösung besteht darin, eine benutzerdefinierte Codierung (siehe PEP 263 ) zu erstellen, wie dieses Rezept zeigt. Sie können dies wie folgt implementieren:
Nachdem dieser Code ausgeführt wurde (z. B. können Sie ihn in Ihre .pythonrc- oder site.py-Datei einfügen), wird jeder Code, der mit dem Kommentar "# encoding: mylang" beginnt, automatisch durch den obigen Vorverarbeitungsschritt übersetzt. z.B.
Vorsichtsmaßnahmen:
Der Präprozessor-Ansatz weist Probleme auf, da Sie wahrscheinlich mit der Arbeit mit dem C-Präprozessor vertraut sind. Das wichtigste ist das Debuggen. Alles, was Python sieht, ist die vorverarbeitete Datei, was bedeutet, dass der in der Stapelverfolgung usw. gedruckte Text darauf verweist. Wenn Sie eine signifikante Übersetzung durchgeführt haben, unterscheidet sich diese möglicherweise stark von Ihrem Quelltext. Das obige Beispiel ändert keine Zeilennummern usw., ist also nicht zu unterschiedlich. Je mehr Sie es ändern, desto schwieriger wird es, dies herauszufinden.
quelle
myimport
auf einem Modul zu verwenden, das einfach enthält,print 1
da es nur Codezeilen liefert=1 ... SyntaxError: invalid syntax
b=myimport("b.py")
" und b.py, die nur "print 1
" enthalten. Gibt es noch etwas zu dem Fehler (Stack-Trace) ? etc)?import
das eingebaute Modul verwendet__import__
wird. Wenn Sie dies überschreiben ( bevor Sie das Modul importieren, für das der geänderte Import erforderlich ist), benötigen Sie kein separates Modulmyimport
Ja, bis zu einem gewissen Grad ist es möglich. Es ist ein Modul aus , dass es Anwendungen
sys.settrace()
zu implementierengoto
undcomefrom
„keywords“:quelle
Kurz ändern und den Quellcode neu zu kompilieren (das ist mit Open - Source möglich), die Basissprache zu ändern ist nicht wirklich möglich.
Selbst wenn Sie die Quelle neu kompilieren, wäre es nicht Python, sondern nur Ihre gehackte geänderte Version, in die Sie sehr vorsichtig sein müssen, um keine Fehler einzuführen.
Ich bin mir jedoch nicht sicher, warum Sie das möchten. Die objektorientierten Funktionen von Python machen es recht einfach, mit der aktuellen Sprache ähnliche Ergebnisse zu erzielen.
quelle
Allgemeine Antwort: Sie müssen Ihre Quelldateien vorverarbeiten.
Spezifischere Antwort: Installieren Sie EasyExtend und führen Sie die folgenden Schritte aus
i) Erstellen Sie ein neues Langlet (Erweiterungssprache)
Ohne zusätzliche Angabe soll eine Reihe von Dateien unter EasyExtend / langlets / mystmts / erstellt werden.
ii) Öffnen Sie mystmts / parsedef / Grammar.ext und fügen Sie die folgenden Zeilen hinzu
Dies reicht aus, um die Syntax Ihrer neuen Anweisung zu definieren. Das Nicht-Terminal small_stmt ist Teil der Python-Grammatik und der Ort, an dem die neue Anweisung eingebunden ist. Der Parser erkennt nun die neue Anweisung, dh eine Quelldatei, die sie enthält, wird analysiert. Der Compiler wird es jedoch ablehnen, da es noch in gültiges Python umgewandelt werden muss.
iii) Nun muss man die Semantik der Aussage hinzufügen. Dazu muss man msytmts / langlet.py bearbeiten und einen my_stmt-Knotenbesucher hinzufügen.
iv) CD zu Langlets / Mystmts und Typ
Nun soll eine Sitzung gestartet werden und die neu definierte Anweisung kann verwendet werden:
Ein paar Schritte, um zu einer trivialen Aussage zu kommen, oder? Es gibt noch keine API, mit der man einfache Dinge definieren kann, ohne sich um Grammatiken kümmern zu müssen. Aber EE ist sehr zuverlässig modulo einige Fehler. Es ist also nur eine Frage der Zeit, bis eine API entsteht, mit der Programmierer bequeme Dinge wie Infix-Operatoren oder kleine Anweisungen mit nur bequemer OO-Programmierung definieren können. Für komplexere Dinge wie das Einbetten ganzer Sprachen in Python durch Erstellen eines Langlets gibt es keine Möglichkeit, einen vollständigen Grammatikansatz zu umgehen.
quelle
Hier ist eine sehr einfache, aber beschissene Möglichkeit, neue Anweisungen nur im Interpretationsmodus hinzuzufügen . Ich verwende es für kleine 1-Buchstaben-Befehle zum Bearbeiten von Genanmerkungen nur mit sys.displayhook, aber nur um diese Frage beantworten zu können, habe ich sys.excepthook auch für die Syntaxfehler hinzugefügt. Letzteres ist wirklich hässlich und holt den Rohcode aus dem Readline-Puffer. Der Vorteil ist, dass es einfach ist, auf diese Weise neue Anweisungen hinzuzufügen.
quelle
Ich habe eine Anleitung zum Hinzufügen neuer Anweisungen gefunden:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
Grundsätzlich müssen Sie bearbeiten, um neue Anweisungen hinzuzufügen
Python/ast.c
(unter anderem) die Python-Binärdatei und neu kompilieren.Während es möglich ist, nicht. Sie können fast alles über Funktionen und Klassen erreichen (für die keine Python neu kompiliert werden muss, nur um Ihr Skript auszuführen.)
quelle
Dies ist mit EasyExtend möglich :
quelle
Es werden nicht gerade neue Anweisungen zur Sprachsyntax hinzugefügt, aber Makros sind ein leistungsstarkes Werkzeug: https://github.com/lihaoyi/macropy
quelle
Nicht ohne den Interpreter zu modifizieren. Ich weiß, dass viele Sprachen in den letzten Jahren als "erweiterbar" beschrieben wurden, aber nicht so, wie Sie es beschreiben. Sie erweitern Python, indem Sie Funktionen und Klassen hinzufügen.
quelle
Es gibt eine auf Python basierende Sprache namens Logix, mit der Sie solche Dinge tun können. Es ist schon eine Weile nicht mehr in der Entwicklung, aber die Funktionen, nach denen Sie gefragt haben, funktionieren mit der neuesten Version.
quelle
Einige Dinge können mit Dekorateuren gemacht werden. Nehmen wir zB an, Python hatte keine
with
Aussage. Wir könnten dann ein ähnliches Verhalten wie folgt implementieren:Es ist jedoch eine ziemlich unreine Lösung, wie hier gemacht. Insbesondere das Verhalten, bei dem der Dekorateur die Funktion aufruft und auf setzt
_
,None
ist unerwartet. Zur Verdeutlichung: Dieser Dekorateur entspricht dem SchreibenVon Dekorateuren wird normalerweise erwartet, dass sie Funktionen modifizieren und nicht ausführen.
Ich habe eine solche Methode zuvor in einem Skript verwendet, in dem ich das Arbeitsverzeichnis für mehrere Funktionen vorübergehend festlegen musste.
quelle
Vor zehn Jahren konnte man das nicht, und ich bezweifle, dass sich das geändert hat. Allerdings war es damals nicht so schwer, die Syntax zu ändern, wenn Sie bereit waren, Python neu zu kompilieren, und ich bezweifle, dass sich dies auch geändert hat.
quelle