2 points par GN⁺ 2024-07-26 | 1 commentaires | Partager sur WhatsApp
  • WAT est un inspector conçu pour identifier rapidement la nature d’objets inconnus dans le runtime Python, avec en un coup d’œil le type, la valeur, les attributs, les méthodes, les types parents, les signatures, la documentation et même le code source
  • L’usage de base est wat / object, équivalent à wat(object), et l’outil prend aussi en charge plusieurs syntaxes comme wat.short / 'foo', 'foo' | wat.short, wat('foo', short=True)
  • On peut chaîner des modifiers comme .short, .dunder, .long, .code, .caller, .public, .all, .ret, .str pour ajuster l’étendue de la sortie, le mode de retour, l’affichage en couleur et l’indication de l’emplacement d’appel
  • L’installation se fait avec pip install wat puis import wat, et pour du débogage rapide il est aussi possible de coller un snippet Insta-Load dans une session Python pour l’utiliser immédiatement sans installation dans cette même session
  • Des exemples avec User de Django, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str], str | None montrent que WAT peut servir au débogage, à l’exploration REPL et à l’apprentissage des internals de Python

Ce que fait WAT

  • WAT est un outil pour explorer et inspecter des objets Python à l’exécution
  • Lorsqu’il est difficile de comprendre ce qu’est un objet inconnu, on peut enquêter sur sa nature dans la console Python avec l’inspector wat
  • En exécutant wat / object sur un object quelconque, on peut voir les informations suivantes
    • le type de l’objet
    • la valeur formatée
    • les variables et les méthodes
    • les types parents
    • la signature
    • la documentation
    • le code source
  • La même inspection approfondie peut aussi s’utiliser avec la syntaxe wat(object)
  • Wat est présenté comme une variante anglaise de what, employée pour exprimer la confusion ou le mécontentement

Usage de base et syntaxe

  • L’opérateur de division est utilisé pour une saisie rapide
    • wat / foo est équivalent à wat(foo)
  • Plusieurs syntaxes permettent d’obtenir la même inspection
    • wat.short / 'foo' : syntaxe pratique pour aller vite
    • wat.short('foo')
    • wat('foo', short=True) : syntaxe Python naturelle
    • 'foo' | wat.short : syntaxe de type pipe Unix
  • On peut ajuster le comportement d’inspection avec la forme wat.modifier / foo
  • Les modifiers peuvent être chaînés, par exemple wat.short.str.gray / 'foo'
  • En Python, les objets ne se limitent pas aux structures de données : ils incluent aussi fonctions, classes, modules et types intégrés, donc wat peut explorer n’importe quel objet
  • Saisir wat dans l’interpréteur affiche l’aide sur l’objet wat lui-même

Ajuster le périmètre d’inspection avec les modifiers

  • .short ou .s masque les attributs comme les variables et méthodes internes, et n’affiche que la valeur, le type, les types parents, la signature et la documentation
  • .dunder affiche les attributs dunder commençant par __
  • .long affiche les valeurs et docstrings sans les tronquer
  • .code affiche le code source des fonctions, méthodes et classes
  • .nodocs masque la documentation des fonctions et des classes
  • .caller montre comment et où l’inspection a été appelée, et fonctionne dans un fichier plutôt que dans le REPL
  • .public masque les attributs private et n’affiche que les attributs publics
  • .all inclut toutes les informations possibles
  • .ret renvoie de nouveau l’objet après l’inspection
  • .str renvoie la chaîne de résultat au lieu de l’afficher
  • .gray désactive l’affichage en couleur dans la console
  • .color force l’affichage en couleur dans la console
  • wat.locals inspecte les variables locales, et wat.globals les variables globales

Installation et Insta-Load

  • Le flux d’installation via pip est le suivant
    • pip install wat
    • dans Python, import wat
  • Le package wat n’a aucune dépendance externe
  • Pour du débogage rapide, il propose une méthode Insta-Load qui permet de l’utiliser dans la même session Python sans installation
  • Insta-Load consiste à coller dans l’interpréteur un snippet Python qui importe base64 et zlib, reconstruit une chaîne de code compressée et encodée, puis l’exécute avec exec(..., globals())
  • Après exécution du snippet Insta-Load, l’objet wat devient disponible
  • Il est recommandé de vérifier ce qui va être exécuté avant de lancer le snippet
    • on peut prévisualiser le code extrait avec print(zlib.decompress(base64.b64decode(code)).decode())
    • coller le contenu de inspection.py dans l’interpréteur produit le même effet
    • il est aussi proposé d’installer le package avec pip puis de relire le code
  • WAT peut être chargé à partir d’un seul glyphe Unicode
  • Le loader basé sur une chaîne Unicode convertit une longue séquence d’emojis ou de caractères combinés en octets via ord(c) & 255, puis exécute le résultat après zlib.decompress(...) avec exec(...)

Identifier le type d’un objet et comprendre son usage

  • En Python, langage à typage dynamique, il peut être difficile d’identifier le type d’un objet, et WAT Inspector affiche le nom du type ainsi que le module dont il provient
  • Les exemples de vérification de type montrent la valeur, le type et la longueur
    • wat.short / (1,) affiche la valeur (1,), le type tuple, la longueur 1
    • wat.short / {None} affiche la valeur {None}, le type set, la longueur 1
  • Dans l’exemple d’objet Django User, wat.short / user affiche str: admin, repr: <User: admin>, le type django.contrib.auth.models.User, ainsi que la liste des types parents
  • Une fois le type réel identifié, on peut ajouter des annotations de type dans le code pour réduire la confusion par la suite
  • Pour comprendre comment utiliser un objet inconnu, on peut afficher la liste de ses méthodes, sa signature et sa docstring
    • wat / ['foo'] est donné en exemple
    • pour voir la docstring complète, il faut utiliser wat.long
  • Pour comprendre l’usage d’une fonction, on peut consulter sa docstring et sa signature
    • wat / str.split est donné en exemple

Exploration des attributs, modules et code source

  • Pour voir l’intérieur de l’objet inspecté, on peut lister ses attributs ainsi que le type de chaque attribut
    • wat / re.match('(\\d)_(.*)', '1_title') est donné en exemple
  • L’outil peut aussi servir à explorer des modules en listant leurs fonctions, classes et sous-modules
    • il y a un exemple avec import pathlib puis wat / pathlib
    • on peut ensuite approfondir avec wat / pathlib.fnmatch
  • WAT Inspector masque par défaut les attributs commençant par __
    • wat.dunder / {} permet d’afficher les attributs dunder
  • Pour voir concrètement comment une fonction fonctionne, on peut afficher son code source
    • il y a un exemple avec import colorsys puis wat.code / colorsys.hsv_to_rgb
  • Les dict et list imbriqués sont formatés avec une indentation lisible

Session de débogage et inspection des variables

  • Après avoir lancé le débogueur interactif de Python avec breakpoint(), on peut inspecter les objets directement sur place
  • Dans l’exemple Pdb, après import wat ou collage du snippet Insta-Load, wat / foo sert à inspecter une variable locale, puis c permet de reprendre l’exécution
  • Les variables locales et globales peuvent être consultées via wat.locals et wat.globals
  • Appeler wat() sans argument affiche les variables locales de la pile appelante sous le titre Local variables

Exemples d’apprentissage des internals Python

  • Le contenu inclut des exemples à visée pédagogique pour comprendre le fonctionnement interne de Python
  • reversed([]) == reversed([]) vaut False, et wat.s / reversed([]) montre que la valeur est un objet list_reverseiterator et que son type est list_reverseiterator
  • wat / type('ObjectCreator', (), {}) affiche la valeur de la classe créée dynamiquement, le type type, et la signature: class ObjectCreator()
  • wat / type affiche la valeur de type, son type type, la signature class type(…), la documentation type(object) -> the object's type, type(name, bases, dict, **kwds) -> a new type, ainsi que des attributs publics comme mro
  • wat.s / List[str] affiche la valeur typing.List[str], le type typing._GenericAlias, les types parents typing._BaseGenericAlias, typing._Final, ainsi que la signature def List(*args, **kwargs)
  • wat(str | None) affiche la valeur str | None et le type types.UnionType
  • Des exemples d’exploration d’objets intégrés Python sont aussi donnés avec wat / __builtins__, wat / ...
  • WAT peut aussi s’inspecter lui-même
    • exemples : wat.dunder / wat, wat.code / wat.__truediv__

Résumé du fonctionnement interne

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False) construit le résultat d’inspection sous forme de chaîne
    • avec all=True, dunder, long, code et caller sont activés ensemble
    • avec public=True, l’affichage des éléments private est désactivé
    • si sys.stdout.isatty() renvoie vrai, la largeur du terminal est récupérée et des séparateurs sont ajoutés au-dessus et au-dessous de la sortie
  • La sortie d’inspection est générée dans l’ordre suivant : valeur de l’objet, représentation chaîne, type, types parents, longueur, signature, documentation, code source, puis section des attributs
  • L’inspection des attributs parcourt dir(obj) par ordre alphabétique
    • les attributs dunder sont exclus si l’option dunder est désactivée
    • les attributs private commençant par _ sont exclus si l’option private est désactivée
    • si getattr(obj, key) lève BaseException, l’objet exception est utilisé comme valeur
  • Pour les objets callable, la signature est formatée à partir de inspect.signature(obj)
    • en cas d’échec, une signature de repli de la forme (...) est renvoyée
    • les classes reçoivent le préfixe class , les coroutine functions le préfixe async def , et les fonctions, méthodes, builtins ou objets disposant de __name__ le préfixe def
  • Avec code=True, si l’objet est une classe ou un callable, le code source est affiché via inspect.getsource(obj)
    • en cas de OSError, TypeError ou IndentationError, un message d’échec est renvoyé
  • Les formateurs de dict et list renvoient ERROR: too deeply nested si la profondeur d’indentation dépasse 30

Sortie couleur et thème

  • L’affichage couleur peut être contrôlé via des variables d’environnement
    • WAT_COLOR="false" désactive l’affichage couleur dans la console
    • WAT_COLOR="true" force la couleur même dans un environnement non TTY
  • La variable d’environnement WAT_COLORS permet de personnaliser le thème de couleurs
  • Le thème par défaut est un mapping de codes couleur ANSI sous la forme 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) retire les séquences d’échappement ANSI avec une expression régulière

Inspiration

1 commentaires

 
GN⁺ 2024-07-26
Commentaires sur Hacker News
  • Waouh, c’est vraiment bien. J’utilisais autrefois python-ls[0] pour un usage similaire, mais quelque chose s’est cassé pour une raison dont je ne me souviens plus, et il n’est plus maintenu
    Je compte l’ajouter à ma boîte à outils de débogage, principalement composée de snoop[1] et pdbpp. Ce que j’aimerais voir dans wat, c’est une sorte de widget ipy qui facilite l’exploration d’objets dans Jupyter
    J’aime aussi le hack d’exec en base64. J’utilise Python depuis longtemps, mais je n’y avais jamais pensé et je ne l’avais jamais vu jusque-là ; je vais certainement m’en servir pour quelques usages à l’avenir
    [0] https://github.com/gabrielcnr/python-ls
    [1] https://pypi.org/project/snoop/

  • Ça a l’air intéressant. J’utilise tout le temps dir en Python, et quand la documentation est médiocre, c’est parfois plus utile que la doc officielle
    Le shell interactif est l’un des vrais points forts de Python, et il est surprenant qu’il n’y ait pas davantage de nouveaux outils ou d’innovations autour de lui

    • Il y a aussi la fonction help(). Vraiment utile
  • On dirait une version plus sophistiquée du vieux icecream
    https://github.com/gruns/icecream
    Si vous ne connaissez pas, regardez aussi plus bas la liste des implémentations pour d’autres langages
    https://github.com/gruns/icecream#icecream-in-other-language...

  • Ce genre d’outil est utile
    Il y a 20 ans, j’avais créé un inspecteur d’objets pour Zope
    Aujourd’hui, j’utilise devtools tous les jours, et icecream ainsi que q de temps en temps. Je vais aussi essayer wat

  • from wat import wat
    Étant donné le côté très cool du projet, je suis surpris qu’il ne propose pas simplement import wat avec la même syntaxe d’utilisation. Cela aurait pu permettre aux utilisateurs curieux de découvrir le truc en essayant wat/wat

    • import wat serait bien, mais Python a la limitation de ne pas permettre de rendre un module appelable. C’est pour cela qu’on en arrive au plus long from wat import wat
      Je n’en suis pas certain, mais import wat; wat.wat / object serait peut-être plus pratique
  • Ça a l’air très utile, mais suis-je le seul à être agacé par la tendance récente à surcharger des opérateurs sans aucun rapport, ici l’opérateur /, au nom de la lisibilité ?

    • Je suis d’accord que, dans ce cas, la surcharge de / est un choix étrange. Cela dit, il est dommage de ne pas pouvoir surcharger is. En pratique, wat(foo) aurait probablement suffi
  • Pour éviter l’import fastidieux, vous pouvez aussi ajouter ceci à votre fichier $PYTHONSTARTUP
    try:
    from wat import wat
    except ImportError:
    pass

    • On peut même ajouter un importeur inline en base64 assez sympa
      Au final, j’ai imprimé sa sortie et je l’ai placée dans un répertoire pointé par PYTHONPATH pour qu’elle soit toujours disponible
      Reste à voir si je continuerai à m’en servir
  • Waouh, si un outil comme celui-ci avait existé quand j’apprenais Python, cela aurait changé la donne. Quand on apprend un langage, voir ce qui se passe à l’intérieur est un passage essentiel, et le débogage de base de Python est, au mieux, décevant
    À la place, j’ai installé pry et je suis devenu un fervent fan de Ruby, mais cet outil pourrait me donner envie de réessayer Python

  • L’auteur utilise en interne le module Python inspect de la bibliothèque standard pour fournir ces fonctionnalités. Bien sûr, il ajoute beaucoup de valeur par-dessus
    Voir inspection.py dans le module wat
    À la 2e ligne, on trouve :
    import inspect as std_inspect

  • « Si vous voulez déboguer quelque chose rapidement, vous pouvez utiliser cet inspecteur dans la même session sans rien installer »
    « Collez cet extrait dans l’interpréteur Python pour le charger à la volée »
    L’idée de mettre dans le README du projet une copie complète du projet sous forme de données compressées encodées en base64 est assez ingénieuse
    C’est particulièrement adapté à ce genre de projet, qu’on n’aurait pas forcément pensé à préinstaller dans l’environnement où il deviendra indispensable