2 Punkte von GN⁺ 2024-07-26 | 1 Kommentare | Auf WhatsApp teilen
  • WAT ist ein Inspector, mit dem sich die Natur unbekannter Objekte in der Python-Runtime schnell bestimmen lässt; Typ, Wert, Attribute, Methoden, Eltern-Typen, Signatur, Dokumentation und Quellcode können auf einmal geprüft werden
  • Die grundlegende Verwendung ist wat / object und verhält sich genauso wie wat(object); unterstützt werden außerdem mehrere Syntaxvarianten wie wat.short / 'foo', 'foo' | wat.short, wat('foo', short=True)
  • Mit Modifizierern wie .short, .dunder, .long, .code, .caller, .public, .all, .ret, .str lässt sich die Ausgabe verketten und in Umfang, Rückgabeart, Farbausgabe und Anzeige der Aufrufposition steuern
  • Die Installation erfolgt mit pip install wat und anschließend import wat; für schnelles Debugging kann auch ein Insta-Load-Snippet in eine Python-Session eingefügt werden, um es in derselben Session ohne Installation zu verwenden
  • Beispiele wie Django User, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str], str | None zeigen, dass WAT sich für Debugging, REPL-Erkundung und zum Lernen von Python-Interna eignet

Was WAT macht

  • WAT ist ein Werkzeug zum Erkunden und Inspizieren von Python-Objekten zur Laufzeit
  • Wenn sich schwer erkennen lässt, was ein unbekanntes Objekt ist, kann man in der Python-Konsole mit dem wat-Inspector dessen Natur untersuchen
  • Führt man für ein beliebiges object wat / object aus, lassen sich folgende Informationen prüfen
    • der type des Objekts
    • der formatierte Wert
    • Variablen und Methoden
    • Eltern-Typen
    • Signatur
    • Dokumentation
    • Quellcode
  • Dieselbe tiefgehende Inspektion ist auch mit der Syntax wat(object) möglich
  • Wat wird als Abwandlung des englischen what beschrieben, das verwendet wird, um Verwirrung oder Unmut auszudrücken

Grundlegende Verwendung und Syntax

  • Für schnelle Eingabe wird der Divisionsoperator verwendet
    • wat / foo ist dasselbe wie wat(foo)
  • Für dieselbe Inspektion können mehrere Syntaxvarianten verwendet werden
    • wat.short / 'foo': Syntax für schnelle Eingabe
    • wat.short('foo')
    • wat('foo', short=True): natürliche Python-Syntax
    • 'foo' | wat.short: Syntax im Stil einer Unix-Pipe
  • Mit Formen wie wat.modifier / foo lässt sich das Verhalten der Inspektion anpassen
  • Modifizierer können verkettet werden; ein Beispiel ist wat.short.str.gray / 'foo'
  • Da Objekte in Python nicht nur Datenstrukturen, sondern auch Funktionen, Klassen, Module und eingebaute Typen umfassen, kann wat beliebige Objekte erkunden
  • Gibt man im Interpreter wat ein, erhält man Hilfe zum wat-Objekt selbst

Mit Modifizierern den Inspektionsumfang steuern

  • .short oder .s blendet Attribute wie interne Variablen und Methoden aus und gibt nur Wert, Typ, Eltern-Typen, Signatur und Dokumentation aus
  • .dunder zeigt dunder-Attribute an, die mit __ beginnen
  • .long zeigt nicht gekürzte Werte und Docstrings an
  • .code zeigt den Quellcode von Funktionen, Methoden und Klassen
  • .nodocs blendet die Dokumentation von Funktionen und Klassen aus
  • .caller zeigt, wie und an welcher Stelle die Inspektion aufgerufen wurde, und funktioniert außerhalb der REPL in Dateien
  • .public blendet private Attribute aus und zeigt nur öffentliche Attribute
  • .all enthält alle verfügbaren Informationen
  • .ret gibt nach der Inspektion das Objekt erneut zurück
  • .str gibt statt der Ausgabe den Ergebnis-String zurück
  • .gray deaktiviert die Farbausgabe in der Konsole
  • .color erzwingt die Farbausgabe in der Konsole
  • wat.locals inspiziert lokale Variablen, wat.globals globale Variablen

Installation und Insta-Load

  • Der Ablauf für die Installation mit pip ist wie folgt
    • pip install wat
    • in Python import wat
  • Das wat-Paket hat keine externen Abhängigkeiten
  • Für schnelles Debugging gibt es eine Insta-Load-Methode, die sich in derselben Python-Session ohne Installation verwenden lässt
  • Insta-Load funktioniert, indem nach dem Import von base64 und zlib ein komprimierter und kodierter Code-String wiederhergestellt und mit exec(..., globals()) ausgeführt wird; dazu wird ein Python-Snippet in den Interpreter eingefügt
  • Nach Ausführung des Insta-Load-Snippets kann das wat-Objekt verwendet werden
  • Es wird empfohlen, vor der Ausführung zu prüfen, was ausgeführt wird
    • Mit print(zlib.decompress(base64.b64decode(code)).decode()) lässt sich der extrahierte Code vorab ansehen
    • Das Einfügen des Inhalts von inspection.py in den Interpreter hat denselben Effekt
    • Als weitere Methode wird vorgeschlagen, das Paket mit pip zu installieren und den Code zu prüfen
  • WAT kann aus einem einzelnen Unicode-Glyph geladen werden
  • Der Unicode-String-basierte Loader wandelt eine lange Zeichenfolge aus Emojis und kombinierenden Zeichen mit ord(c) & 255 in Bytes um, dekomprimiert sie mit zlib.decompress(...) und führt sie danach mit exec(...) aus

Objekttyp und Verwendung verstehen

  • In Python als dynamisch typisierter Sprache kann es schwierig sein, den Typ eines Objekts zu erkennen; der WAT Inspector zeigt den Typnamen und das Modul, aus dem dieser Typ stammt
  • Die Beispiele zur Typprüfung zeigen Wert, Typ und Länge gemeinsam an
    • wat.short / (1,) gibt den Wert (1,), den Typ tuple und die Länge 1 aus
    • wat.short / {None} gibt den Wert {None}, den Typ set und die Länge 1 aus
  • Im Beispiel mit dem Django-User-Objekt gibt wat.short / user str: admin, repr: <User: admin>, den Typ django.contrib.auth.models.User und eine Liste der Eltern-Typen aus
  • Nachdem der tatsächliche Typ bestätigt wurde, können Typannotationen in den Code aufgenommen werden, um spätere Verwirrung zu verringern
  • Wenn man die Verwendung eines unbekannten Objekts verstehen will, lassen sich Methodenliste, Signatur und Docstring ausgeben
    • Als Beispiel wird wat / ['foo'] gezeigt
    • Wenn man den vollständigen Docstring sehen möchte, verwendet man wat.long
  • Um die Verwendung einer Funktion zu verstehen, kann man Docstring und Signatur der Funktion ansehen
    • Als Beispiel wird wat / str.split gezeigt

Attribute, Module und Quellcode erkunden

  • Um das Innere eines inspizierten Objekts zu sehen, können Attribute und die Typen der einzelnen Attribute aufgelistet werden
    • Als Beispiel wird wat / re.match('(\\d)_(.*)', '1_title') gezeigt
  • Das Tool kann auch zur Modulerkundung verwendet werden; Funktionen, Klassen und Untermodule des gewählten Moduls können aufgelistet werden
    • Es gibt ein Beispiel mit import pathlib gefolgt von wat / pathlib
    • Danach kann man tiefer einsteigen, etwa mit wat / pathlib.fnmatch
  • Der WAT Inspector blendet Attribute, die mit __ beginnen, standardmäßig aus
    • Mit wat.dunder / {} lassen sich dunder-Attribute anzeigen
  • Um zu sehen, wie eine Funktion tatsächlich arbeitet, kann man ihren Quellcode ansehen
    • Es gibt ein Beispiel mit import colorsys gefolgt von wat.code / colorsys.hsv_to_rgb
  • Verschachtelte dicts und lists werden in einer eingerückten, gut lesbaren Form formatiert

Debugging-Sitzungen und Variableninspektion

  • Nach dem Start eines interaktiven Debuggers mit breakpoint() in Python können Objekte direkt an Ort und Stelle inspiziert werden
  • Im Pdb-Beispiel wird nach import wat oder dem Einfügen des Insta-Load-Snippets mit wat / foo eine lokale Variable inspiziert und mit c die Ausführung fortgesetzt
  • Lokale und globale Variablen können jeweils mit wat.locals und wat.globals geprüft werden
  • Wenn wat() ohne Argumente aufgerufen wird, werden die lokalen Variablen des aufrufenden Stacks unter dem Titel Local variables ausgegeben

Lernbeispiele zu Python-Interna

  • Es sind Beispiele für den Einsatz zum Verständnis der internen Funktionsweise von Python enthalten
  • reversed([]) == reversed([]) ergibt False, und wat.s / reversed([]) zeigt, dass der Wert ein Objekt vom Typ list_reverseiterator ist
  • wat / type('ObjectCreator', (), {}) zeigt den Wert der dynamisch erzeugten Klasse, den Typ type und die Signatur signature: class ObjectCreator()
  • wat / type zeigt den Wert von type selbst, den Typ type, die Signatur class type(…), die Dokumentation type(object) -> the object's type, type(name, bases, dict, **kwds) -> a new type sowie öffentliche Attribute wie mro
  • wat.s / List[str] zeigt den Wert typing.List[str], den Typ typing._GenericAlias, die Eltern-Typen typing._BaseGenericAlias, typing._Final und die Signatur def List(*args, **kwargs)
  • wat(str | None) zeigt den Wert str | None und den Typ types.UnionType
  • Als Beispiele für die Erkundung eingebauter Python-Objekte werden wat / __builtins__ und wat / ... gezeigt
  • Auch WAT selbst kann inspiziert werden
    • Beispiele sind wat.dunder / wat und wat.code / wat.__truediv__

Zusammenfassung der internen Funktionsweise

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False) baut das Ergebnis der Objektinspektion als String auf
    • Wenn all=True, werden dunder, long, code und caller gemeinsam aktiviert
    • Wenn public=True, wird die Ausgabe privater Elemente deaktiviert
    • Wenn sys.stdout.isatty() wahr ist, wird die Terminalbreite ermittelt und ober- und unterhalb der Ausgabe eine Trennlinie aus hinzugefügt
  • Die Inspektionsausgabe wird in der Reihenfolge Objektwert, String-Darstellung, Typ, Eltern-Typen, Länge, Signatur, Dokumentation, Quellcode und Attributabschnitte erzeugt
  • Die Attributinspektion durchläuft dir(obj) in alphabetischer Reihenfolge
    • dunder-Attribute werden ausgeschlossen, wenn die Einstellung dunder deaktiviert ist
    • private Attribute, die mit _ beginnen, werden ausgeschlossen, wenn die Einstellung für private Ausgaben deaktiviert ist
    • Wenn bei getattr(obj, key) eine BaseException auftritt, wird das Ausnahmeobjekt als Wert verwendet
  • Für aufrufbare Objekte wird die Signatur auf Basis von inspect.signature(obj) formatiert
    • Im Fehlerfall wird eine Ersatzsignatur in der Form (...) zurückgegeben
    • Klassen erhalten das Präfix class , Coroutine-Funktionen async def , Funktionen, Methoden, Builtins und Objekte mit __name__ das Präfix def
  • Wenn code=True gesetzt ist und das Objekt eine Klasse oder callable ist, wird der Quellcode mit inspect.getsource(obj) ausgegeben
    • Bei OSError, TypeError oder IndentationError wird eine Fehlermeldung zurückgegeben
  • Die Formatter für dict und list geben bei einer Einrückungstiefe von mehr als 30 ERROR: too deeply nested zurück

Farbausgabe und Themes

  • Die Farbausgabe kann über Umgebungsvariablen gesteuert werden
    • WAT_COLOR="false" deaktiviert die Farbausgabe in der Konsole
    • WAT_COLOR="true" erzwingt Farbausgabe auch in Nicht-TTY-Umgebungen
  • Mit der Umgebungsvariable WAT_COLORS lässt sich das Farbthema anpassen
  • Das Standard-Theme ist eine Zuordnung von ANSI-Farbcodes in der Form BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,VARIABLE=1;33,CODE=0;33
  • _strip_color(text) entfernt ANSI-Escape-Sequenzen per regulärem Ausdruck

Inspiration

1 Kommentare

 
GN⁺ 2024-07-26
Hacker-News-Kommentare
  • Wow, wirklich großartig. Ich habe früher für einen ähnlichen Zweck python-ls[0] verwendet, aber aus einem Grund, an den ich mich nicht mehr erinnere, ging irgendetwas kaputt, und es wird auch nicht mehr gewartet.
    Kommt auf jeden Fall in meine Debugging-Toolbox, die hauptsächlich aus snoop[1] und pdbpp besteht. Was ich mir noch von wat wünschen würde, wäre ein ipy-Widget, das die Objekterkundung in Jupyter einfacher macht.
    Auch der base64-exec-Hack gefällt mir. Ich benutze Python schon lange, aber darauf gekommen oder darüber gestolpert bin ich bisher nie, daher werde ich ihn künftig sicher für ein paar Dinge verwenden.
    [0] https://github.com/gabrielcnr/python-ls
    [1] https://pypi.org/project/snoop/

  • Sieht interessant aus. Ich benutze in Python ständig dir, und wenn die Dokumentation dürftig ist, ist das manchmal nützlicher als die offizielle Doku.
    Die interaktive Shell ist eine der echten Stärken von Python, deshalb überrascht es mich, dass es darum herum nicht mehr neue Tools oder Innovationen gibt.

    • Es gibt auch die help()-Funktion. Die ist wirklich nützlich.
  • Sieht aus wie eine schickere Version des alten icecream.
    https://github.com/gruns/icecream
    Falls du es nicht kennst, lohnt sich auch ein Blick auf die Liste der Implementierungen für andere Sprachen weiter unten.
    https://github.com/gruns/icecream#icecream-in-other-language...

  • Solche Tools sind nützlich.
    Vor 20 Jahren habe ich für Zope einen Objekt-Introspektor gebaut.
    Heute benutze ich devtools täglich und icecream und q gelegentlich. wat werde ich auch mal ausprobieren.

  • from wat import wat
    Bei einem Projekt mit so viel Stil ist es überraschend, dass es nicht einfach import wat mit derselben Nutzungsweise anbietet. Dann könnten neugierige Nutzer mit wat/wat herumspielen und den Trick entdecken.

    • import wat wäre schön, aber in Python gibt es die Einschränkung, dass sich Module nicht aufrufbar machen lassen. Deshalb ist es bei dem längeren from wat import wat gelandet.
      Ich bin nicht ganz sicher, aber import wat; wat.wat / object könnte sogar praktischer sein.
  • Sieht sehr nützlich aus, aber bin ich der Einzige, den der neuere Trend stört, völlig unzusammenhängende Operatoren im Namen der Lesbarkeit zu überladen, hier also den /-Operator?

    • Ich stimme zu, dass das Überladen von / hier eine seltsame Wahl ist. Schade nur, dass man is nicht überladen kann. Realistisch gesehen hätte wat(foo) auch völlig gereicht.
  • Um den lästigen Import zu vermeiden, kann man der Datei $PYTHONSTARTUP auch Folgendes hinzufügen:
    try:
    from wat import wat
    except ImportError:
    pass

    • Man kann sogar den ziemlich coolen base64-Inline-Importer hinzufügen.
      Ich habe am Ende einfach dessen Ausgabe ausgegeben und in ein Verzeichnis gelegt, auf das PYTHONPATH zeigt, damit es immer verfügbar ist.
      Mal sehen, ob ich es dauerhaft weiterverwende.
  • Wow, wenn es so ein Tool gegeben hätte, als ich Python gelernt habe, hätte das alles verändert. Wenn man eine Sprache lernt, ist es ein zentraler Weg zu sehen, was intern passiert, und Pythons eingebaute Debugging-Möglichkeiten sind selbst wohlwollend betrachtet eher enttäuschend.
    Stattdessen habe ich pry installiert und bin zu einem begeisterten Ruby-Fan geworden, aber dieses Tool könnte mich dazu bringen, Python noch einmal eine Chance zu geben.

  • Der Autor verwendet intern das inspect-Modul der Python-Standardbibliothek, um die Funktionalität bereitzustellen. Natürlich hat er darauf noch viel zusätzlichen Mehrwert aufgebaut.
    Siehe inspection.py im wat-Modul.
    In Zeile 2 steht:
    import inspect as std_inspect

  • „Wenn du schnell etwas debuggen willst, kannst du diesen Inspektor verwenden, ohne in derselben Sitzung irgendetwas zu installieren.“
    „Füge dieses Snippet in den Python-Interpreter ein, um es on the fly zu laden.“
    Die Idee, im README des Projekts eine vollständige Kopie des gesamten Projekts als base64-kodierte komprimierte Daten unterzubringen, ist ziemlich clever.
    Das passt besonders gut zu Projekten dieser Art, bei denen man vielleicht nicht daran gedacht hat, sie vorab in genau die Umgebung zu legen, in der man sie am dringendsten brauchen wird.