1 Punkte von GN⁺ 2024-08-23 | 1 Kommentare | Auf WhatsApp teilen

Pythons Präprozessor

  • Die Behauptung, Python habe keinen Präprozessor, stimmt nicht
  • Python verfügt über einen sehr mächtigen Präprozessor

Kodierung von Python-Quellcode

  • Dank PEP-0263 kann die Quellcode-Kodierung definiert werden
  • Die Kodierung kann durch einen Magic Comment in den ersten beiden Zeilen festgelegt werden
  • Beispiele: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :

Path Configuration Files (.pth)

  • Wenn der Python-Interpreter ohne die Option -S gestartet wird, lädt er automatisch das Paket site
  • Durch Hinzufügen einer .pth-Datei im Ordner site-packages kann der Modulsuchpfad erweitert werden
  • Zeilen in einer .pth-Datei, die mit import beginnen, werden ausgeführt
  • Dadurch lässt sich beim Initialisieren des Python-Interpreters beliebiger Code ausführen

Benutzerdefinierte Codecs definieren

  • Der Python-Interpreter verlangt zwei Dinge:
    • eine Funktion decode(data: bytes) -> tuple[str, int]
    • eine Klasse für inkrementelles Decoding
  • Mit codecs.utf_8_decode wird das eigentliche Decoding durchgeführt und der resultierende String an den Präprozessor übergeben
  • Es ist sinnvoll, Ausnahmen abzufangen, auszugeben und anschließend erneut auszulösen

Einen inkrementellen Decoder bereitstellen

  • Durch Vererbung von codecs.BufferedIncrementalDecoder kann ein inkrementeller Decoder implementiert werden
  • Dabei werden Daten im Puffer gesammelt und die gesamte Datei beim abschließenden Decode-Aufruf vorverarbeitet

Python erweitern

  • Es ist vergleichsweise einfach, Python mit der Standardbibliothek zu erweitern
  • Mit dem Modul tokenize kann der Token-Stream einer Datei verändert werden, oder mit dem Modul ast der abstrakte Syntaxbaum
Unäres Inkrement und Dekrement
  • Python hat keine unären Inkrement- und Dekrement-Operatoren
  • x++, x-- sind ungültig
  • ++x, --x sind gültig, haben aber eine andere Bedeutung
  • Ausdrücke mit unärem Inkrement und Dekrement können in Python-Ausdrücke umgewandelt werden
Beispiel
  • Eingabedatei incdec.py:
    # coding: magic.incdec
    i = 6
    assert i-- == 6
    assert i == 5
    assert ++i == 6
    assert --i == 5
    assert i++ == 5
    assert i == 6
    assert (++i, 'i++') == (7, 'i++')
    print("PASSED")
    
  • Umgewandelte Datei:
    i = 6
    assert ((i, i := i - 1)[0]) == 6
    assert i == 5
    assert ((i, i := i + 1)[1]) == 6
    assert ((i, i := i - 1)[1]) == 5
    assert ((i, i := i + 1)[0]) == 5
    assert i == 6
    assert (((i, i := i + 1)[1]), 'i++') == (7, 'i++')
    print("PASSED")
    

Python mit geschweiften Klammern (Bython)

  • Statt Einrückung kann Python geschweifte Klammern zur Bereichsdefinition verwenden
  • Mit tokenize.generate_tokens kann der Token-Stream verändert werden
Beispiel
  • Eingabedatei test.by:
    # coding: magic.braces
    def print_message(num_of_times) {
      for i in range(num_of_times) {
        print("braces ftw")
      }
      print({'x': 3})
    }
    x = {
      'foo': 42,
      'bar': 5
    }
    if __name__ == "__main__" {
      print_message(2)
      print({k: v for k, v in x.items()})
    }
    
  • Umgewandelte Datei:
    def print_message(num_of_times):
      for i in range(num_of_times):
        print("braces ftw")
      print({'x': 3})
    x = {
      'foo': 42,
      'bar': 5
    }
    if __name__ == "__main__":
      print_message(2)
      print({k: v for k, v in x.items()})
    

Andere Sprachen interpretieren

  • Der Python-Interpreter kann so eingerichtet werden, dass er andere Sprachen interpretiert
  • Beispiele: C, C++, TOML
Beispiel
  • C++-Datei test.cpp:
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    
  • Umgewandelte Datei:
    import cppyy
    cppyy.cppdef(r"""
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    """)
    from cppyy.gbl import main
    if __name__ == "__main__":
      main()
    

Datenvalidierung

  • Daten im TOML-Format können mit JSON Schema validiert werden
  • Die eigentliche Validierung erfolgt mit jsonschema
Beispiel
  • Schema-Datei schema.json:
    {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "age": {"type": "number"},
        "scores": {
          "type": "array",
          "items": {"type": "number"}
        },
        "address": {"$ref": "#/$defs/address"}
      },
      "required": ["name"],
      "$defs": {
        "address": {
          "type": "object",
          "properties": {
            "street": {"type": "string"},
            "postcode": {"type": "number"}
          },
          "required": ["street"]
        }
      }
    }
    
  • Gültige Datendatei data_valid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, 20, 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    
  • Ungültige Datendatei data_invalid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, "20", 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    

Fazit

  • Mit benutzerdefinierten Codecs und Path Configuration Files lässt sich das Verhalten des Python-Interpreters stark verändern
  • Beispiele sind pythonql, future-typing, future-fstrings, future-annotations
  • Mit magic_codec kann man leicht mit eigenen Präprozessoren experimentieren

Zusammenfassung von GN⁺

  • Mithilfe von Pythons Präprozessor lassen sich verschiedene Spracherweiterungen und Datenvalidierungen umsetzen
  • Es wird erklärt, wie sich das Verhalten des Python-Interpreters über benutzerdefinierte Codecs verändern lässt
  • Der Artikel bietet Python-Entwicklern nützliche Werkzeuge und Techniken
  • Projekte mit ähnlicher Funktionalität sind unter anderem pythonql und future-typing

1 Kommentare

 
GN⁺ 2024-08-23
Hacker-News-Kommentare
  • Die Syntaxfehlermeldung für from __future__ import braces ist seit 2001 fest in cpython einkodiert

    • Sie wurde von Jeremy Hylton geschrieben, der heute als Senior Engineer bei Google für die Qualität der KI-Suche verantwortlich ist
    • Erstaunlich, dass sich die Karriere einer Person über 24 Jahre hinweg von der Verewigung eines bestimmten Syntaxverbots hin zur Arbeit an Suchsystemen entwickelt hat, die keine spezielle Syntax brauchen
  • Ich habe über kreative Methoden zur Kündigung mithilfe von import-hooks nachgedacht, war aber enttäuscht, dass der codec-regex Dinge wie μtf8 nicht zulässt

    • Man muss also import hooks, Preprocessors und sys.settrace verwenden, um jede Funktion per Monkey-Patching durch die zuvor aufgerufene Funktion zu ersetzen und alle 17 Minuten stdout und stderr auszutauschen
  • Es gibt einen Grund dafür, dass Python keine Preprocessor-Hooks offenlegt, und ich denke, vernünftige Erwachsene sollten sie meiden

    • Aber unabhängig von vernünftigen Erwachsenen möchte ich einfach Spaß haben
  • Ein Preprocessor ist praktischer und nützlicher

    • Ich habe damit gehackt, Code mit dem ast-Modul umzuschreiben, ihn mit exec auszuführen und danach exit() einzufügen
    • Bevor alle dicts geordnet waren, habe ich AST-Rewriting dafür nützlich eingesetzt
  • Ich liebe die Flexibilität von Python

    • Die am meisten verfluchte Aufgabe war, Strings in place zu verändern, und ich habe mmap missbraucht, damit sich das Skript selbst verändert
    • Jetzt möchte ich einen Lisp-Interpreter schreiben
  • Der beste Anwendungsfall mit pyxl ist von jsx inspiriert

    • Mit # coding: pyxl kann man HTML-Code schreiben
  • Ich frage mich, ob der Übergang von Python 2 zu 3 besser hätte gehandhabt werden können

    • # coding: six.python2 könnte Python-2-Code in Python 3 gültig machen, und # coding: six.python3 könnte Python-3-Code so anpassen, dass er unter Python 2 lauffähig ist
  • Ich freue mich, dass euch diese Idee gefällt, bald kommt mehr dazu

  • Es ist das erste Mal seit Langem, dass ich von einer völlig neuen Idee überrascht bin

  • Wenn man in Python Inline-Codegenerierung möchte, kann man cogs von Ned Batchelder verwenden

  • Ich frage mich, ob Abhängigkeiten, die durch diese coding-hook-Strategie eingeführt werden, von pip freeze oder uv erkannt werden

    • Falls nicht, viel Spaß damit. Es könnte einfacher sein, die Bibliothek einfach umzuschreiben (wenn es solche Fallstricke gibt, gibt es vermutlich auch noch andere)