PEP 511 – API für Code-Transformer
- Autor:
- Victor Stinner <vstinner at python.org>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 04-Jan-2016
- Python-Version:
- 3.6
Ablehnungsbescheid
Dieser PEP wurde von seinem Autor abgelehnt.
Dieser PEP wurde als Ermöglichung neuer, Python-ähnlicher Programmiersprachen gesehen, die zwar nah, aber inkompatibel mit regulärem Python sind. Es wurde entschieden, Syntaxen, die mit Python inkompatibel sind, nicht zu fördern.
Dieser PEP wurde auch als ein nützliches Werkzeug zum Experimentieren mit neuen Python-Features angesehen, aber es ist bereits möglich, diese ohne den PEP, nur mit `importlib`-Hooks, zu experimentieren. Wenn eine Funktion nützlich wird, sollte sie direkt Teil von Python sein, anstatt von einem externen Python-Modul abzuhängen.
Schließlich wurde dieser PEP vom FAT Python Optimization Project vorangetrieben, das 2016 aufgegeben wurde, da es nicht möglich war, signifikante Geschwindigkeitssteigerungen zu zeigen, aber auch wegen des Mangels an Zeit, die fortschrittlichsten und komplexesten Optimierungen zu implementieren.
Zusammenfassung
Schlägt eine API zur Registrierung von Bytecode- und AST-Transformern vor. Fügt außerdem die Befehlszeilenoption `-o OPTIM_TAG hinzu, um .pyc-Dateinamen zu ändern. `-o noopt deaktiviert den Peephole-Optimierer. Löst beim Import eine `ImportError`-Ausnahme aus, wenn die .pyc-Datei fehlt und die zur Transformation des Codes erforderlichen Code-Transformer fehlen. Code-Transformer werden nicht benötigt, um den transformierten Code vorab (aus .pyc-Dateien) zu laden.
Begründung
Python bietet keine standardmäßige Methode zur Code-Transformation. Projekte, die Code transformieren, verwenden verschiedene Hooks. Das MacroPy-Projekt verwendet einen Import-Hook: Es fügt seinen eigenen Modul-Finder zu sys.meta_path hinzu, um seinen AST-Transformer einzuhaken. Eine andere Option ist, die eingebaute compile()-Funktion per Monkey-Patching zu modifizieren. Es gibt noch mehr Optionen, um einen Code-Transformer einzuhaken.
Python 3.4 fügte der importlib.abc.SourceLoader eine compile_source()-Methode hinzu. Code-Transformation ist jedoch breiter als nur das Importieren von Modulen, siehe die unten beschriebenen Anwendungsfälle.
Das Schreiben eines Optimierers oder Präprozessors liegt außerhalb des Umfangs dieses PEPs.
Anwendungsfall 1: AST-Optimierer
Die Transformation eines Abstract Syntax Tree (AST) ist eine praktische Methode zur Implementierung eines Optimierers. Es ist einfacher, mit dem AST als mit dem Bytecode zu arbeiten, da der AST mehr Informationen enthält und auf einer höheren Ebene angesiedelt ist.
Da die Optimierung im Voraus erfolgen kann, können komplexe, aber langsame Optimierungen implementiert werden.
Beispiele für Optimierungen, die mit einem AST-Optimierer implementiert werden können
- Kopierfortpflanzung: Ersetzt `
x=1; y=xdurch `x=1; y=1 - Konstantenfaltung: Ersetzt `
1+1durch `2 - Beseitigung von totem Code
Unter Verwendung von Guards (siehe PEP 510) ist es möglich, eine viel größere Auswahl an Optimierungen zu implementieren. Beispiele:
- Iterable vereinfachen: Ersetzt `
range(3)durch `(0, 1, 2), wenn es als Iterable verwendet wird. - Schleifenentrollung
- Reine Builtins aufrufen: Ersetzt `
len("abc")durch `3 - Verwendete Builtin-Symbole in Konstanten kopieren
- Siehe auch Optimierungen, die in fatoptimizer implementiert sind, ein statischer Optimierer für Python 3.6.
Die folgenden Probleme können mit einem AST-Optimierer implementiert werden:
- Issue #1346238: Eine Konstantenfaltungs-Optimierungsphase für den AST
- Issue #2181: Lokale Variablen am Ende einer Funktion optimiert entfernen
- Issue #2499: Unäre Operatoren `+` und `not` auf Konstanten falten
- Issue #4264: Patch: Code optimieren, um `LIST_APPEND` zu verwenden, anstatt `list.append` aufzurufen
- Issue #7682: Optimierung von `if`-Anweisungen mit konstanten Ausdrücken
- Issue #10399: AST-Optimierung: Inlining von Funktionsaufrufen
- Issue #11549: Aufbau eines AST-Optimierers, der einige Funktionalitäten aus dem Peephole-Optimierer verschiebt
- Issue #17068: Peephole-Optimierung für konstante Strings
- Issue #17430: Verpasste Peephole-Optimierung
Anwendungsfall 2: Präprozessor
Ein Präprozessor kann einfach mit einem AST-Transformer implementiert werden. Ein Präprozessor hat verschiedene und unterschiedliche Anwendungsfälle.
Einige Beispiele
- Entfernen von Debug-Code wie Assertions und Logs, um den Code für die Produktion schneller zu machen.
- Tail-Call-Optimierung
- Hinzufügen von Profiling-Code
- Lazy Evaluation: siehe lazy_python (Bytecode-Transformer) und lazy macro von MacroPy (AST-Transformer)
- Ändern von Dictionary-Literalen in `collection.OrderedDict`-Instanzen
- Konstanten deklarieren: siehe `@asconstants` von `codetransformer`
- Domänenspezifische Sprachen (DSL) wie SQL-Abfragen. Die Python-Sprache selbst muss nicht geändert werden. Frühere Versuche, DSLs für SQL zu implementieren, wie PEP 335 – Überladbare boolesche Operatoren, wurden abgelehnt.
- Pattern Matching von funktionalen Sprachen
- String-Interpolation, aber PEP 498 wurde in Python 3.6 integriert.
MacroPy hat eine lange Liste von Beispielen und Anwendungsfällen.
Dieser PEP fügt keine neuen Code-Transformer hinzu. Die Verwendung eines Code-Transformers erfordert ein externes Modul und dessen manuelle Registrierung.
Siehe auch PyXfuscator: Python Obfuscator, Deobfuscator und benutzergesteuerter Dekompiler.
Anwendungsfall 3: Deaktivieren aller Optimierungen
Ned Batchelder bat darum, eine Option zum Deaktivieren des Peephole-Optimierers hinzuzufügen, da dieser die Implementierung von Code Coverage erschwert. Siehe die Diskussion in der Python-Ideas-Mailingliste: Alle Peephole-Optimierungen deaktivieren.
Dieser PEP fügt eine neue Befehlszeilenoption `-o noopt hinzu, um den Peephole-Optimierer zu deaktivieren. In Python ist das so einfach wie
sys.set_code_transformers([])
Dies wird Issue #2506 beheben: Mechanismus zum Deaktivieren von Optimierungen hinzufügen.
Anwendungsfall 4: Neue Bytecode-Optimierer in Python schreiben
Python 3.6 optimiert den Code mit einem Peephole-Optimierer. Per Definition hat ein Peephole-Optimierer eine eng begrenzte Sicht auf den Code und kann daher nur grundlegende Optimierungen implementieren. Der Optimierer schreibt den Bytecode neu. Es ist schwierig, ihn zu erweitern, da er in C geschrieben ist.
Mit diesem PEP wird es möglich, einen neuen Bytecode-Optimierer in reinem Python zu implementieren und neue Optimierungen zu experimentieren.
Einige Optimierungen sind auf dem AST einfacher zu implementieren, wie z. B. Konstantenfaltung, aber Optimierungen auf dem Bytecode sind immer noch nützlich. Zum Beispiel können, wenn der AST in Bytecode kompiliert wird, nutzlose Sprünge erzeugt werden, da der Compiler naiv ist und versucht, nichts zu optimieren.
Anwendungsfälle
Dieser Abschnitt gibt Beispiele für Anwendungsfälle, die erklären, wann und wie Code-Transformer verwendet werden.
Interaktiver Interpreter
Code-Transformer können mit dem interaktiven Interpreter verwendet werden, der in Python beliebt und üblicherweise zur Demonstration von Python verwendet wird.
Der Code wird zur Laufzeit transformiert, daher kann der Interpreter langsamer sein, wenn teure Code-Transformer verwendet werden.
Erstellen eines transformierten Pakets
Es wird möglich sein, ein Paket mit transformiertem Code zu erstellen.
Ein Transformer kann eine Konfiguration haben. Die Konfiguration wird nicht im Paket gespeichert.
Alle .pyc-Dateien des Pakets müssen mit denselben Code-Transformern und derselben Transformer-Konfiguration transformiert werden.
Es ist möglich, verschiedene .pyc-Dateien mit unterschiedlichen Optimizer-Tags zu erstellen. Beispiel: `fat für die Standardkonfiguration und `fat_inline für eine andere Konfiguration mit aktivierter Funktionsinlining.
Ein Paket kann .pyc-Dateien mit verschiedenen Optimizer-Tags enthalten.
Installieren eines Pakets, das transformierte .pyc-Dateien enthält
Es wird möglich sein, ein Paket zu installieren, das transformierte .pyc-Dateien enthält.
Alle .pyc-Dateien mit einem beliebigen Optimizer-Tag im Paket werden installiert, nicht nur die für den aktuellen Optimizer-Tag.
Erstellen von .pyc-Dateien bei der Installation eines Pakets
Wenn ein Paket keine .pyc-Dateien für den aktuellen Optimizer-Tag enthält (oder einige .pyc-Dateien fehlen), werden die .pyc-Dateien während der Installation erstellt.
Code-Transformer des Optimizer-Tags sind erforderlich. Andernfalls schlägt die Installation mit einem Fehler fehl.
Ausführen transformierten Codes
Es wird möglich sein, transformierten Code auszuführen.
Löst beim Import eine `ImportError`-Ausnahme aus, wenn die .pyc-Datei des aktuellen Optimizer-Tags fehlt und die zur Transformation des Codes erforderlichen Code-Transformer fehlen.
Der interessante Punkt hierbei ist, dass Code-Transformer nicht benötigt werden, um den transformierten Code auszuführen, wenn alle erforderlichen .pyc-Dateien bereits verfügbar sind.
Code-Transformer-API
Ein Code-Transformer ist eine Klasse mit den Methoden ast_transformer() und/oder code_transformer() (API siehe unten) und einem name-Attribut.
Definieren Sie aus Effizienzgründen keine code_transformer()- oder ast_transformer()-Methode, wenn sie nichts tut.
Das name-Attribut (str) muss ein kurzer String sein, der zur Identifizierung eines Optimierers verwendet wird. Es wird zum Erstellen eines .pyc-Dateinamens verwendet. Der Name darf keine Punkte (`.`), Bindestriche (`-`) oder Verzeichnistrennzeichen enthalten: Punkte werden zur Trennung von Feldern in einem .pyc-Dateinamen verwendet, und Bindestriche werden zur Verbindung von Code-Transformernamen verwendet, um den Optimizer-Tag zu erstellen.
Hinweis
Es wäre schön, den vollqualifizierten Namen eines Moduls im *Kontext* zu übergeben, wenn ein AST-Transformer zum Transformieren eines Moduls beim Import verwendet wird, aber es scheint, dass diese Information in PyParser_ASTFromStringObject() nicht verfügbar ist.
code_transformer()-Methode
Prototyp
def code_transformer(self, code, context):
...
new_code = ...
...
return new_code
Parameter
- code: Code-Objekt
- context: Ein Objekt mit einem `optimize`-Attribut (
int), der Optimierungsstufe (0, 1 oder 2). Der Wert des `optimize`-Attributs stammt vom `optimize`-Parameter dercompile()-Funktion, standardmäßig ist er gleichsys.flags.optimize.
Jede Implementierung von Python kann dem *Kontext* zusätzliche Attribute hinzufügen. Zum Beispiel wird unter CPython der *Kontext* auch das folgende Attribut haben:
- interactive (
bool): Wahr, wenn im interaktiven Modus
XXX weitere Flags hinzufügen?
XXX Flags-Integer durch einen Sub-Namespace oder spezifische Attribute ersetzen?
Die Methode muss ein Code-Objekt zurückgeben.
Der Code-Transformer wird nach der Kompilierung in Bytecode ausgeführt.
ast_transformer()-Methode
Prototyp
def ast_transformer(self, tree, context):
...
return tree
Parameter
- tree: Ein AST-Baum
- context: Ein Objekt mit einem `filename`-Attribut (
str)
Es muss einen AST-Baum zurückgeben. Es kann den AST-Baum vor Ort modifizieren oder einen neuen AST-Baum erstellen.
Der AST-Transformer wird nach der Erstellung des AST durch den Parser und vor der Kompilierung in Bytecode aufgerufen. Neue Attribute können zukünftig zum *Kontext* hinzugefügt werden.
Änderungen
Kurz gesagt, fügen Sie hinzu:
- -o OPTIM_TAG Befehlszeilenoption
- sys.implementation.optim_tag
- sys.get_code_transformers()
- sys.set_code_transformers(transformers)
- ast.PyCF_TRANSFORMED_AST
API zum Abrufen/Setzen von Code-Transformern
Neue Funktionen zum Registrieren von Code-Transformern hinzufügen
sys.set_code_transformers(transformers): Legt die Liste der Code-Transformer fest und aktualisiertsys.implementation.optim_tagsys.get_code_transformers(): Ruft die Liste der Code-Transformer ab.
Die Reihenfolge der Code-Transformer ist wichtig. Wenn Transformer A und dann Transformer B ausgeführt wird, kann dies zu einem anderen Ergebnis führen als wenn Transformer B und dann Transformer A ausgeführt wird.
Beispiel zum Voranstellen eines neuen Code-Transformers
transformers = sys.get_code_transformers()
transformers.insert(0, new_cool_transformer)
sys.set_code_transformers(transformers)
Alle AST-Transformer werden sequenziell ausgeführt (z. B. der zweite Transformer erhält die Eingabe des ersten Transformers), und dann werden alle Bytecode-Transformer sequenziell ausgeführt.
Optimizer-Tag
Änderungen
- Fügen Sie
sys.implementation.optim_tag(str) hinzu: Optimierungs-Tag. Der Standard-Optimierungs-Tag ist'opt'. - Fügen Sie eine neue Befehlszeilenoption `
-o OPTIM_TAGhinzu, umsys.implementation.optim_tagzu setzen.
Änderungen an importlib
importlibverwendetsys.implementation.optim_tag, um den.pyc-Dateinamen für das Importieren von Modulen zu erstellen, anstatt immeroptzu verwenden. Entfernen Sie auch den Sonderfall für die Optimierungsstufe0mit dem Standard-Optimizer-Tag'opt', um den Code zu vereinfachen.- Beim Laden eines Moduls, wenn die
.pyc-Datei fehlt, aber die.py-Datei verfügbar ist, wird die.py-Datei nur verwendet, wenn die Code-Optimierer denselben Optimizer-Tag wie der aktuelle Tag haben, andernfalls wird eine `ImportError`-Ausnahme ausgelöst.
Pseudocode einer `use_py()`-Funktion, um zu entscheiden, ob eine .py-Datei kompiliert werden kann, um ein Modul zu importieren
def transformers_tag():
transformers = sys.get_code_transformers()
if not transformers:
return 'noopt'
return '-'.join(transformer.name
for transformer in transformers)
def use_py():
return (transformers_tag() == sys.implementation.optim_tag)
Die Reihenfolge von sys.get_code_transformers() ist wichtig. Zum Beispiel ergibt der fat-Transformer gefolgt vom pythran-Transformer den Optimizer-Tag fat-pythran.
Das Verhalten des `importlib`-Moduls bleibt mit dem Standard-Optimizer-Tag ('opt') unverändert.
Peephole-Optimierer
Standardmäßig ist sys.implementation.optim_tag opt und sys.get_code_transformers() gibt eine Liste eines Code-Transformers zurück: den Peephole-Optimierer (optimiert den Bytecode).
Verwenden Sie `-o noopt, um den Peephole-Optimierer zu deaktivieren. In diesem Fall ist der Optimizer-Tag noopt und kein Code-Transformer ist registriert.
Die Verwendung der Option `-o opt hat keine Auswirkung.
AST-Erweiterungen
Erweiterungen zur Vereinfachung der Implementierung von AST-Transformern
- Fügen Sie ein neues Compiler-Flag
PyCF_TRANSFORMED_ASThinzu, um den transformierten AST zu erhalten.PyCF_ONLY_ASTgibt den AST vor den Transformern zurück.
Beispiele
.pyc-Dateinamen
Beispiele für .pyc-Dateinamen des `os`-Moduls.
Mit dem Standard-Optimizer-Tag 'opt'
| .pyc-Dateiname | Optimierungsstufe |
|---|---|
os.cpython-36.opt-0.pyc |
0 |
os.cpython-36.opt-1.pyc |
1 |
os.cpython-36.opt-2.pyc |
2 |
Mit dem Optimizer-Tag `'fat'
| .pyc-Dateiname | Optimierungsstufe |
|---|---|
os.cpython-36.fat-0.pyc |
0 |
os.cpython-36.fat-1.pyc |
1 |
os.cpython-36.fat-2.pyc |
2 |
Bytecode-Transformer
Gruseliger Bytecode-Transformer, der alle Strings durch `"Ni! Ni! Ni!" ersetzt
import sys
import types
class BytecodeTransformer:
name = "knights_who_say_ni"
def code_transformer(self, code, context):
consts = ['Ni! Ni! Ni!' if isinstance(const, str) else const
for const in code.co_consts]
return types.CodeType(code.co_argcount,
code.co_kwonlyargcount,
code.co_nlocals,
code.co_stacksize,
code.co_flags,
code.co_code,
tuple(consts),
code.co_names,
code.co_varnames,
code.co_filename,
code.co_name,
code.co_firstlineno,
code.co_lnotab,
code.co_freevars,
code.co_cellvars)
# replace existing code transformers with the new bytecode transformer
sys.set_code_transformers([BytecodeTransformer()])
# execute code which will be transformed by code_transformer()
exec("print('Hello World!')")
Ausgabe
Ni! Ni! Ni!
AST-Transformer
Ähnlich wie beim Bytecode-Transformer ersetzt auch der AST-Transformer alle Strings durch `"Ni! Ni! Ni!"
import ast
import sys
class KnightsWhoSayNi(ast.NodeTransformer):
def visit_Str(self, node):
node.s = 'Ni! Ni! Ni!'
return node
class ASTTransformer:
name = "knights_who_say_ni"
def __init__(self):
self.transformer = KnightsWhoSayNi()
def ast_transformer(self, tree, context):
self.transformer.visit(tree)
return tree
# replace existing code transformers with the new AST transformer
sys.set_code_transformers([ASTTransformer()])
# execute code which will be transformed by ast_transformer()
exec("print('Hello World!')")
Ausgabe
Ni! Ni! Ni!
Andere Python-Implementierungen
Der PEP 511 sollte von allen Python-Implementierungen umgesetzt werden, aber der Bytecode und der AST sind nicht standardisiert.
Übrigens gibt es selbst zwischen minoren Versionen von CPython Änderungen an der AST-API. Es gibt Unterschiede, aber nur geringfügige. Es ist ziemlich einfach, einen AST-Transformer zu schreiben, der beispielsweise unter Python 2.7 und Python 3.5 funktioniert.
Diskussion
- [Python-Ideas] PEP 511: API for code transformers (Januar 2016)
- [Python-Dev] AST optimizer implemented in Python (August 2012)
Vorhandene Lösungen
AST-Optimierer
Das Issue #17515 „Add sys.setasthook() to allow to use a custom AST“ optimizer war ein erster Versuch einer API für Code-Transformer, aber speziell für AST.
Im Jahr 2015 schrieb Victor Stinner das Projekt fatoptimizer, einen AST-Optimierer, der Funktionen mithilfe von Guards spezialisiert.
Im Jahr 2014 erstellte Kevin Conway den PyCC-Optimierer.
Im Jahr 2012 schrieb Victor Stinner das Projekt astoptimizer, einen AST-Optimierer, der verschiedene Optimierungen implementiert. Die interessantesten Optimierungen verletzen die Python-Semantik, da kein Guard verwendet wird, um die Optimierung zu deaktivieren, wenn sich etwas ändert.
Im Jahr 2011 schlug Eugene Toder vor, einige Peephole-Optimierungen in einem neuen AST-Optimierer neu zu schreiben: Issue #11549, Build-out an AST optimizer, moving some functionality out of the peephole optimizer. Der Patch fügt ast.Lit hinzu (es wurde vorgeschlagen, es in ast.Literal umzubenennen).
Python-Präprozessoren
- MacroPy: MacroPy ist eine Implementierung von Syntaktischen Makros in der Python-Programmiersprache. MacroPy bietet einen Mechanismus für benutzerdefinierte Funktionen (Makros), um Transformationen am abstrakten Syntaxbaum (AST) eines Python-Programms zur Importzeit durchzuführen.
- pypreprocessor: C-Style Präprozessor-Direktiven in Python, wie `#define` und `#ifdef`
Bytecode-Transformer
- codetransformer: Bytecode-Transformer für CPython, inspiriert von `NodeTransformer` des `ast`-Moduls.
- byteplay: Byteplay ermöglicht es Ihnen, Python-Codeobjekte in äquivalente Objekte umzuwandeln, mit denen leicht gearbeitet werden kann, und diese Objekte wieder in lebende Python-Codeobjekte zurückzuwandeln. Es ist nützlich, um verrückte Transformationen auf Python-Funktionen anzuwenden, und auch nützlich, um die Feinheiten des Python-Bytecodes zu lernen. Siehe Byteplay-Dokumentation.
Siehe auch
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0511.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT