Können Sie der Python-Syntax neue Anweisungen hinzufügen?

124

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)

dbr
quelle
10
In einem etwas verwandten Punkt ist ein Anwendungsfall, in dem es nützlich sein könnte, neue Anweisungen im laufenden Betrieb zu erstellen (im Gegensatz zu einer ernsthaften "Erweiterung" der Sprache), für Personen, die den interaktiven Interpreter als Taschenrechner oder sogar als Betriebssystem-Shell verwenden . Ich erstelle oft kleine Wegwerffunktionen im laufenden Betrieb, um etwas zu tun, das ich wiederholen werde, und in solchen Situationen wäre es schön, sehr abgekürzte Befehle wie Makros oder Anweisungen zu erstellen, anstatt die langen Namen mit function () -Syntax einzugeben. Natürlich ist Py nicht wirklich dafür gedacht ... aber die Leute verbringen viel Zeit damit, es interaktiv zu nutzen.
Kilo
5
@Kilo es könnte sich lohnen, sich ipython anzuschauen - es hat viele Shell'ish-Funktionen, zum Beispiel können Sie reguläre "ls" - und "cd" -Befehle, Tab-Vervollständigung, viele makro-ische Funktionen usw. verwenden
dbr
Einige Sprachen sind exquisit erweiterbar, z. B. Forth und Smalltalk, aber ihre Sprachparadigmen unterscheiden sich auch von denen, die Python verwendet. Mit beiden werden neue Wörter (Forth) oder Methoden (Smalltalk) zu einem integralen, ununterscheidbaren Bestandteil der Sprache für diese Installation. So wird jede Forth- oder Smalltalk-Installation im Laufe der Zeit zu einer einzigartigen Kreation. Auch Forth basiert auf RPN. Aber in Anlehnung an DSLs sollte so etwas in Python möglich sein. Aber wie andere hier gesagt haben, warum?
1
Als jemand, der sowohl Python als auch Forth fließend beherrscht und in den vergangenen Jahren mehrere Forth-Compiler implementiert hat, kann ich hier mit einem gewissen Maß an Autorität einen Beitrag leisten. Ohne uneingeschränkten Zugriff auf den internen Parser von Python ist dies völlig unmöglich. Sie können es durch Vorverarbeitung vortäuschen, wie die folgenden (ehrlich gesagt, eher raffinierten!) Antworten zeigen, aber eine echte Aktualisierung der Syntax und / oder Semantik der Sprache in einem heißen Interpreter ist nicht möglich. Dies ist sowohl Pythons Fluch als auch sein Vorteil gegenüber Lisp- und Forth-ähnlichen Sprachen.
Samuel A. Falvo II

Antworten:

153

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ügenuntil 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 Aussage

Einige Sprachen, wie Ruby, haben eine untilAussage, die die Ergänzung zu while( until num == 0entspricht while num != 0) ist. In Ruby kann ich schreiben:

num = 3
until num == 0 do
  puts num
  num -= 1
end

Und es wird gedruckt:

3
2
1

Daher möchte ich Python eine ähnliche Funktion hinzufügen. Das heißt, schreiben zu können:

num = 3
until num == 0:
  print(num)
  num -= 1

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 Datei Grammar/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 untilAnweisung hinzuzufügen . Ich fand heraus, wo die whileAnweisung definiert wurde ( while_stmt) und fügte until_stmtunten hinzu [2] :

compound_stmt: if_stmt | while_stmt | until_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
until_stmt: 'until' test ':' suite

[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 whilemuss, muss auch getan werdenuntil , dient es als ziemlich gute Richtlinie.

Beachten Sie, dass ich beschlossen habe, die elseKlausel von meiner Definition von auszuschließen until, 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_stmtzu schließen until_stmt, wie Sie im Snippet oben sehen können. Es ist gleich while_stmtwieder da.

Wenn Sie laufen makenach der Änderung Grammar/Grammar, Ankündigung , dass das pgenProgramm auszuführen ist neu zu generieren Include/graminit.hund Python/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.asdlder die Struktur der ASTs von Python definiert, und einen AST-Knoten für unsere neue untilAnweisung hinzufügen , ebenfalls direkt unter while:

| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)

Wenn Sie jetzt ausführen make, beachten Sie , dass vor dem Kompilieren einer Reihe von Dateien Parser/asdl_c.pyC-Code aus der AST-Definitionsdatei generiert wird. Dies Grammar/Grammarist ein weiteres Beispiel für den Python-Quellcode, der eine Minisprache (mit anderen Worten DSL) verwendet, um die Programmierung zu vereinfachen. Da Parser/asdl_c.pyes 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.pyder Code zum Verwalten unseres neu definierten AST-Knotens (in die Dateien Include/Python-ast.hund Python/Python-ast.c) generiert wurde , müssen wir den Code, der einen relevanten Analysebaumknoten konvertiert, von Hand schreiben. Dies erfolgt in der Datei Python/ast.c. Dort ast_for_stmtkonvertiert eine Funktion mit dem Namen Analysebaumknoten für Anweisungen in AST-Knoten. Wiederum whilespringen wir , geleitet von unserem alten Freund , direkt in die große Richtung, switchum zusammengesetzte Anweisungen zu verarbeiten, und fügen eine Klausel hinzu für until_stmt:

case while_stmt:
    return ast_for_while_stmt(c, ch);
case until_stmt:
    return ast_for_until_stmt(c, ch);

Jetzt sollten wir implementieren ast_for_until_stmt. Hier ist es:

static stmt_ty
ast_for_until_stmt(struct compiling *c, const node *n)
{
    /* until_stmt: 'until' test ':' suite */
    REQ(n, until_stmt);

    if (NCH(n) == 4) {
        expr_ty expression;
        asdl_seq *suite_seq;

        expression = ast_for_expr(c, CHILD(n, 1));
        if (!expression)
            return NULL;
        suite_seq = ast_for_suite(c, CHILD(n, 3));
        if (!suite_seq)
            return NULL;
        return Until(expression, suite_seq, LINENO(n), n->n_col_offset, c->c_arena);
    }

    PyErr_Format(PyExc_SystemError,
                 "wrong number of tokens for 'until' statement: %d",
                 NCH(n));
    return NULL;
}

Auch dies wurde codiert, während das Äquivalent genau betrachtet wurde ast_for_while_stmt, mit dem Unterschied, dass untilich mich entschieden habe, die elseKlausel nicht zu unterstützen . Wie erwartet wird der AST rekursiv erstellt, wobei andere AST-Erstellungsfunktionen wie ast_for_exprder Bedingungsausdruck und ast_for_suiteder Hauptteil der untilAnweisung verwendet werden. Schließlich wird ein neuer Knoten mit dem Namen Untilzurückgegeben.

Beachten Sie, dass wir nmit einigen Makros wie NCHund auf den Analysebaumknoten zugreifen CHILD. Diese sind verständlich - ihr Code ist in Include/node.h.

Exkurs: AST-Zusammensetzung

Ich habe mich entschieden, einen neuen AST-Typ für die untilAnweisung 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:

until condition:
   # do stuff

Ist funktional äquivalent zu:

while not condition:
  # do stuff

Anstatt den UntilKnoten in zu erstellen ast_for_until_stmt, hätte ich als Kind einen NotKnoten mit einem WhileKnoten 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 an whilefinden wir die Funktion compiler_visit_stmt, die für das Kompilieren von Anweisungen in Bytecode verantwortlich ist. Wir fügen eine Klausel hinzu für Until:

case While_kind:
    return compiler_while(c, s);
case Until_kind:
    return compiler_until(c, s);

Wenn Sie sich fragen, was Until_kindist, ist es eine Konstante (eigentlich ein Wert der _stmt_kindAufzählung), die automatisch aus der AST-Definitionsdatei in generiert wird Include/Python-ast.h. Wie auch immer, wir nennen compiler_untildas natürlich noch nicht. Ich werde gleich darauf zurückkommen.

Wenn Sie neugierig sind wie ich, werden Sie feststellen, dass compiler_visit_stmtdas eigenartig ist. Keine Menge von grep-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 dem VISITMakro, das definiert ist in Python/compile.c:

#define VISIT(C, TYPE, V) {\
    if (!compiler_visit_ ## TYPE((C), (V))) \
        return 0; \

Es wird verwendet , aufzurufen compiler_visit_stmtin compiler_body. Zurück zu unserem Geschäft jedoch ...

Wie versprochen, hier ist compiler_until:

static int
compiler_until(struct compiler *c, stmt_ty s)
{
    basicblock *loop, *end, *anchor = NULL;
    int constant = expr_constant(s->v.Until.test);

    if (constant == 1) {
        return 1;
    }
    loop = compiler_new_block(c);
    end = compiler_new_block(c);
    if (constant == -1) {
        anchor = compiler_new_block(c);
        if (anchor == NULL)
            return 0;
    }
    if (loop == NULL || end == NULL)
        return 0;

    ADDOP_JREL(c, SETUP_LOOP, end);
    compiler_use_next_block(c, loop);
    if (!compiler_push_fblock(c, LOOP, loop))
        return 0;
    if (constant == -1) {
        VISIT(c, expr, s->v.Until.test);
        ADDOP_JABS(c, POP_JUMP_IF_TRUE, anchor);
    }
    VISIT_SEQ(c, stmt, s->v.Until.body);
    ADDOP_JABS(c, JUMP_ABSOLUTE, loop);

    if (constant == -1) {
        compiler_use_next_block(c, anchor);
        ADDOP(c, POP_BLOCK);
    }
    compiler_pop_fblock(c, LOOP, loop);
    compiler_use_next_block(c, end);

    return 1;
}

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 des disModuls, 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 neue untilAnweisung ausprobieren :

>>> until num == 0:
...   print(num)
...   num -= 1
...
3
2
1

Voila, es funktioniert! Sehen wir uns den Bytecode an, der für die neue Anweisung mithilfe des disModuls wie folgt erstellt wurde:

import dis

def myfoo(num):
    until num == 0:
        print(num)
        num -= 1

dis.dis(myfoo)

Hier ist das Ergebnis:

4           0 SETUP_LOOP              36 (to 39)
      >>    3 LOAD_FAST                0 (num)
            6 LOAD_CONST               1 (0)
            9 COMPARE_OP               2 (==)
           12 POP_JUMP_IF_TRUE        38

5          15 LOAD_NAME                0 (print)
           18 LOAD_FAST                0 (num)
           21 CALL_FUNCTION            1
           24 POP_TOP

6          25 LOAD_FAST                0 (num)
           28 LOAD_CONST               2 (1)
           31 INPLACE_SUBTRACT
           32 STORE_FAST               0 (num)
           35 JUMP_ABSOLUTE            3
      >>   38 POP_BLOCK
      >>   39 LOAD_CONST               0 (None)
           42 RETURN_VALUE

Die interessanteste Operation ist Nummer 12: Wenn die Bedingung erfüllt ist, springen wir nach der Schleife zu. Dies ist die richtige Semantik füruntil . 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:

Traceback (most recent call last):
  File "zy.py", line 9, in
    myfoo(3)
  File "zy.py", line 5, in myfoo
    print(num)
SystemError: no locals when loading 'print'

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_Buildin PyAST_Compileruft 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_stmtFunktion in ändern Python/symtable.cund Code für die Behandlung von untilAnweisungen nach dem ähnlichen Code für whileAnweisungen hinzufügen [3] :

case While_kind:
    VISIT(st, expr, s->v.While.test);
    VISIT_SEQ(st, stmt, s->v.While.body);
    if (s->v.While.orelse)
        VISIT_SEQ(st, stmt, s->v.While.orelse);
    break;
case Until_kind:
    VISIT(st, expr, s->v.Until.test);
    VISIT_SEQ(st, stmt, s->v.Until.body);
    break;

[3] : Übrigens, ohne diesen Code gibt es eine Compiler-Warnung für Python/symtable.c. Der Compiler stellt fest, dass der Until_kindAufzählungswert in der switch-Anweisung von nicht behandelt wird, symtable_visit_stmtund 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:

  • PEP 339: Design des CPython-Compilers - wahrscheinlich die wichtigste und umfassendste offizielle Dokumentation für den Python-Compiler. Da es sehr kurz ist, zeigt es schmerzlich den Mangel an guter Dokumentation der Interna von Python.
  • "Python Compiler Internals" - ein Artikel von Thomas Lee
  • "Python: Design and Implementation" - eine Präsentation von Guido van Rossum
  • Python (2.5) Virtual Machine, Eine Führung - eine Präsentation von Peter Tröger

Originalquelle

Eli Bendersky
quelle
7
Ausgezeichneter Artikel (/ Blog), danke! Akzeptieren, da dies die Frage perfekt beantwortet, und die Antworten "Mach das nicht" / "Codierung: mylang" sind bereits hoch bewertet, so dass sie in der Reihenfolge \ o /
dbr
1
Dies ist aber leider keine Antwort. Der verlinkte Artikel ist, aber dass Sie nicht upvoten oder akzeptieren können. Von Antworten, die nur aus einem Link bestehen, wird abgeraten.
Alfe
6
@ Alfe: Dies wurde vor zwei Jahren gepostet, akzeptiert und von 16 Lesern mit +1 bewertet. Beachten Sie, dass es auf meinen eigenen Blog-Beitrag verweist und das Kopieren eines großen Artikels in StackOverflow nicht beabsichtigt ist. Fühlen Sie sich frei, dies in einer nützlichen Bearbeitung zu tun, anstatt Polizei zu spielen.
Eli Bendersky
2
@EliBendersky Nützlich ist eine Untertreibung für diesen Artikel. Vielen Dank, dass Sie so viel darüber erklärt haben, wie diese Dinge in Python tatsächlich funktionieren. Dies hat mir wirklich geholfen, den AST zu verstehen, der für meine aktuelle Arbeit relevant ist. ** auch, falls Sie neugierig sind, meine Version von untilist isa/ isanwie in if something isa dict:oderif something isan int:
Inversus
5
Also, diese Antwort lautet "Schreibe und kompiliere deine eigene Sprache aus der Quelle,
gespalten
53

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:

myprint "This gets logged to file"

wäre gleichbedeutend mit

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

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:

import tokenize

LOGFILE = '/tmp/log.txt'
def translate(readline):
    for type, name,_,_,_ in tokenize.generate_tokens(readline):
        if type ==tokenize.NAME and name =='myprint':
            yield tokenize.NAME, 'print'
            yield tokenize.OP, '>>'
            yield tokenize.NAME, "open"
            yield tokenize.OP, "("
            yield tokenize.STRING, repr(LOGFILE)
            yield tokenize.OP, ","
            yield tokenize.STRING, "'a'"
            yield tokenize.OP, ")"
            yield tokenize.OP, ","
        else:
            yield type,name

(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:

import new
def myimport(filename):
    mod = new.module(filename)
    f=open(filename)
    data = tokenize.untokenize(translate(f.readline))
    exec data in mod.__dict__
    return mod

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:

import codecs, cStringIO, encodings
from encodings import utf_8

class StreamReader(utf_8.StreamReader):
    def __init__(self, *args, **kwargs):
        codecs.StreamReader.__init__(self, *args, **kwargs)
        data = tokenize.untokenize(translate(self.stream.readline))
        self.stream = cStringIO.StringIO(data)

def search_function(s):
    if s!='mylang': return None
    utf8=encodings.search_function('utf8') # Assume utf8 encoding
    return codecs.CodecInfo(
        name='mylang',
        encode = utf8.encode,
        decode = utf8.decode,
        incrementalencoder=utf8.incrementalencoder,
        incrementaldecoder=utf8.incrementaldecoder,
        streamreader=StreamReader,
        streamwriter=utf8.streamwriter)

codecs.register(search_function)

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.

# coding: mylang
myprint "this gets logged to file"
for i in range(10):
    myprint "so does this : ", i, "times"
myprint ("works fine" "with arbitrary" + " syntax" 
  "and line continuations")

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.

Brian
quelle
12
Schön! Anstatt zu sagen, dass man nicht dun sein kann, geben Sie tatsächlich ein paar gute Antworten (das läuft darauf hinaus, dass Sie das wirklich nicht wollen). Upvote.
c0m4
Ich bin nicht sicher, ob ich verstehe, wie das erste Beispiel funktioniert - ich versuche es myimportauf einem Modul zu verwenden, das einfach enthält, print 1da es nur Codezeilen liefert=1 ... SyntaxError: invalid syntax
olamundo
@noam: Ich bin mir nicht sicher, was für dich fehlschlägt - hier wird nur "1" wie erwartet gedruckt. (Dies ist mit den 2 Blöcken, die oben mit "import tokenize" und "import new" beginnen, in Datei a.py sowie " b=myimport("b.py")" und b.py, die nur " print 1" enthalten. Gibt es noch etwas zu dem Fehler (Stack-Trace) ? etc)?
Brian
3
Python3 scheint dies nicht zuzulassen, wenn auch nicht unbedingt absichtlich. Ich erhalte einen Stücklistenfehler.
Tobu
Beachten Sie, dass importdas 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
Tobias Kienzler,
21

Ja, bis zu einem gewissen Grad ist es möglich. Es ist ein Modul aus , dass es Anwendungen sys.settrace()zu implementieren gotound comefrom„keywords“:

from goto import goto, label
for i in range(1, 10):
  for j in range(1, 20):
    print i, j
    if j == 3:
      goto .end # breaking out from nested loop
label .end
print "Finished"
Constantin
quelle
4
Das ist allerdings keine wirklich neue Syntax ... es sieht einfach so aus.
Hans Nowak
3
-1: Die verlinkte Seite hat folgende Überschrift: "Das 'goto'-Modul war ein Aprilscherz, der am 1. April 2004 veröffentlicht wurde. Ja, es funktioniert, aber es ist trotzdem ein Witz. Bitte verwenden Sie es nicht in echtem Code!"
Jim
5
@ Jim könnte eine -1 überdenken. Es weist Sie auf den Implementierungsmechanismus hin. schöne Sache, um damit zu beginnen.
n611x007
14

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.

paxdiablo
quelle
2
In einem Punkt bin ich anderer Meinung. Wenn Sie neue Schlüsselwörter hinzufügen , wäre es meiner Meinung nach immer noch Python. Wenn Sie vorhandene Keywords ändern , ist das, wie Sie sagen, nur gehackt.
Bill the Lizard
9
Wenn Sie neue Schlüsselwörter hinzufügen, handelt es sich um eine von Python abgeleitete Sprache. Wenn Sie Schlüsselwörter ändern, handelt es sich um eine Python-inkompatible Sprache.
Zot
1
Wenn Sie Schlüsselwörter hinzufügen, fehlt Ihnen möglicherweise der Punkt "einfache, leicht zu erlernende Syntax" und "umfangreiche Bibliotheken". Ich denke, Sprachfunktionen sind fast immer ein Fehler (Beispiele sind COBOL, Perl und PHP).
S.Lott
5
Neue Schlüsselwörter würden den Python-Code beschädigen, der sie als Bezeichner verwendet.
Akaihola
12

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)

import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")

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

small_stmt: (expr_stmt | print_stmt  | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | exec_stmt | assert_stmt | my_stmt )

my_stmt: 'mystatement' expr

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.

 def call_my_stmt(expression):
     "defines behaviour for my_stmt"
     print "my stmt called with", expression

 class LangletTransformer(Transformer):
       @transform
       def my_stmt(self, node):
           _expr = find_node(node, symbol.expr)
           return any_stmt(CST_CallFunc("call_my_stmt", [_expr]))

 __publish__ = ["call_my_stmt"]

iv) CD zu Langlets / Mystmts und Typ

python run_mystmts.py

Nun soll eine Sitzung gestartet werden und die neu definierte Anweisung kann verwendet werden:

__________________________________________________________________________________

 mystmts

 On Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
 __________________________________________________________________________________

 my> mystatement 40+2
 my stmt called with 42

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
11

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.


jcomeau@intrepid:~/$ cat demo.py; ./demo.py
#!/usr/bin/python -i
'load everything needed under "package", such as package.common.normalize()'
import os, sys, readline, traceback
if __name__ == '__main__':
    class t:
        @staticmethod
        def localfunction(*args):
            print 'this is a test'
            if args:
                print 'ignoring %s' % repr(args)

    def displayhook(whatever):
        if hasattr(whatever, 'localfunction'):
            return whatever.localfunction()
        else:
            print whatever

    def excepthook(exctype, value, tb):
        if exctype is SyntaxError:
            index = readline.get_current_history_length()
            item = readline.get_history_item(index)
            command = item.split()
            print 'command:', command
            if len(command[0]) == 1:
                try:
                    eval(command[0]).localfunction(*command[1:])
                except:
                    traceback.print_exception(exctype, value, tb)
        else:
            traceback.print_exception(exctype, value, tb)

    sys.displayhook = displayhook
    sys.excepthook = excepthook
>>> t
this is a test
>>> t t
command: ['t', 't']
this is a test
ignoring ('t',)
>>> ^D
jcomeau_ictx
quelle
4

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.)

dbr
quelle
Der eigentliche Link zu PDF - diese "Autonversion" ist kaputt und wurde nach Gottes Kenntnis nun schon lange gebrochen: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX
3

Dies ist mit EasyExtend möglich :

EasyExtend (EE) ist ein Präprozessor-Generator und ein Metaprogrammierungs-Framework, das in reinem Python geschrieben und in CPython integriert ist. Der Hauptzweck von EasyExtend ist die Erstellung von Erweiterungssprachen, dh das Hinzufügen von benutzerdefinierter Syntax und Semantik zu Python.

Matthew Trevor
quelle
1
Wenn Sie diesem Link folgen, wird eine Seite angezeigt: "EasyExtend ist tot. Für diejenigen, die sich für EE interessieren, gibt es ein Nachfolgeprojekt namens Langscape. Anderer Name, vollständige Neugestaltung, gleiche Reise." Da die Gefahr besteht, dass diese Informationsseite nicht mehr funktioniert, ist es möglicherweise eine gute Idee, die Antwort zu aktualisieren.
Celtschk
1

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.

Bill die Eidechse
quelle
1

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.

Claudiu
quelle
Klingt interessant, scheint aber um 2009 gestorben zu sein: web.archive.org/web/20090107014050/http://livelogix.net/logix
Tobias Kienzler
1

Einige Dinge können mit Dekorateuren gemacht werden. Nehmen wir zB an, Python hatte keine withAussage. Wir könnten dann ein ähnliches Verhalten wie folgt implementieren:

# ====== Implementation of "mywith" decorator ======

def mywith(stream):
    def decorator(function):
        try: function(stream)
        finally: stream.close()
    return decorator

# ====== Using the decorator ======

@mywith(open("test.py","r"))
def _(infile):
    for l in infile.readlines():
        print(">>", l.rstrip())

Es ist jedoch eine ziemlich unreine Lösung, wie hier gemacht. Insbesondere das Verhalten, bei dem der Dekorateur die Funktion aufruft und auf setzt _, Noneist unerwartet. Zur Verdeutlichung: Dieser Dekorateur entspricht dem Schreiben

def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.

Von 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.

kdb
quelle
0

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.

Alex Coventry
quelle