2 puntos por GN⁺ 2024-07-26 | 1 comentarios | Compartir por WhatsApp
  • WAT es un inspector para identificar rápidamente objetos desconocidos en el runtime de Python; permite ver de una sola vez el tipo, valor, atributos, métodos, tipos padre, firmas, documentación y código fuente
  • El uso básico es wat / object, que funciona igual que wat(object), y admite varias sintaxis como wat.short / 'foo', 'foo' | wat.short y wat('foo', short=True)
  • Se pueden encadenar modificadores como .short, .dunder, .long, .code, .caller, .public, .all, .ret y .str para ajustar el alcance de la salida, el modo de retorno, la salida con color y la visualización de la ubicación de llamada
  • Se instala con pip install wat y luego import wat; para debugging rápido también se puede pegar un snippet Insta-Load en una sesión de Python y usarlo en esa misma sesión sin instalarlo
  • Ejemplos como Django User, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str] y str | None muestran que WAT puede servir para debugging, exploración en el REPL y aprendizaje de los internos de Python

Qué hace WAT

  • WAT es una herramienta para explorar e inspeccionar objetos de Python en tiempo de ejecución
  • Cuando es difícil entender qué es un objeto desconocido, se puede usar el inspector wat desde la consola de Python para investigar su identidad
  • Al ejecutar wat / object sobre un object cualquiera, se puede ver la siguiente información
    • el tipo del objeto
    • el valor formateado
    • variables y métodos
    • tipos padre
    • firma
    • documentación
    • código fuente
  • La misma inspección profunda también puede usarse con la sintaxis wat(object)
  • Wat se presenta como una variante del inglés what, una expresión usada para manifestar confusión o desagrado

Uso básico y sintaxis

  • Para escribir más rápido, usa el operador de división
    • wat / foo es equivalente a wat(foo)
  • Se pueden usar varias sintaxis para la misma inspección
    • wat.short / 'foo': sintaxis para entrada rápida
    • wat.short('foo')
    • wat('foo', short=True): sintaxis natural de Python
    • 'foo' | wat.short: sintaxis al estilo de pipes de Unix
  • Se puede ajustar el comportamiento de la inspección con la forma wat.modifier / foo
  • Los modificadores pueden encadenarse; un ejemplo es wat.short.str.gray / 'foo'
  • En Python, los objetos incluyen no solo estructuras de datos, sino también funciones, clases, módulos, tipos integrados, etc., por lo que wat puede explorar cualquier objeto
  • Si se escribe wat en el intérprete, se puede ver la ayuda del propio objeto wat

Ajustar el alcance de la inspección con modificadores

  • .short o .s oculta atributos como variables y métodos dentro del objeto, y solo muestra valor, tipo, tipos padre, firma y documentación
  • .dunder muestra los atributos dunder que empiezan con __
  • .long muestra valores y docstrings sin abreviar
  • .code muestra el código fuente de funciones, métodos y clases
  • .nodocs oculta la documentación de funciones y clases
  • .caller muestra cómo y desde dónde se llamó la inspección; funciona en archivos, no en el REPL
  • .public oculta atributos privados y muestra solo atributos públicos
  • .all incluye toda la información posible
  • .ret devuelve de nuevo el objeto después de la inspección
  • .str devuelve la cadena resultante en vez de imprimirla
  • .gray desactiva la salida con color en la consola
  • .color fuerza la salida con color en la consola
  • wat.locals inspecciona variables locales y wat.globals inspecciona variables globales

Instalación e Insta-Load

  • El flujo de instalación con pip es el siguiente
    • pip install wat
    • En Python, import wat
  • El paquete wat no tiene dependencias externas
  • Para debugging rápido, ofrece un método Insta-Load que permite usarlo en la misma sesión de Python sin instalarlo
  • Insta-Load consiste en pegar en el intérprete un snippet de Python que importa base64 y zlib, restaura una cadena de código comprimida y codificada, y la ejecuta con exec(..., globals())
  • Después de ejecutar el snippet de Insta-Load, se puede usar el objeto wat
  • Antes de ejecutar el snippet, se recomienda verificar qué se va a ejecutar
    • Se puede revisar previamente el código extraído con print(zlib.decompress(base64.b64decode(code)).decode())
    • Pegar en el intérprete el contenido de inspection.py tiene el mismo efecto
    • También se propone instalar el paquete con pip y revisar el código
  • WAT puede cargarse desde un único glyph Unicode
  • El loader basado en una cadena Unicode toma una cadena larga de emojis y caracteres combinados, la convierte a bytes con ord(c) & 255, y luego ejecuta zlib.decompress(...) y exec(...)

Entender tipos de objetos y cómo usarlos

  • En Python, al ser un lenguaje de tipado dinámico, a veces es difícil identificar el tipo de un objeto; WAT Inspector muestra el nombre del tipo y el módulo del que proviene
  • Los ejemplos de verificación de tipo muestran juntos el valor, el tipo y la longitud
    • wat.short / (1,) imprime el valor (1,), el tipo tuple y la longitud 1
    • wat.short / {None} imprime el valor {None}, el tipo set y la longitud 1
  • En el ejemplo con un objeto Django User, wat.short / user imprime str: admin, repr: <User: admin>, el tipo django.contrib.auth.models.User y una lista de tipos padre
  • Después de confirmar el tipo real, se pueden agregar anotaciones de tipo al código para reducir confusiones futuras
  • Cuando se quiere entender cómo usar un objeto desconocido, se puede imprimir la lista de métodos, firmas y docstrings
    • Se presenta como ejemplo wat / ['foo']
    • Para ver el docstring completo, se usa wat.long
  • Para entender cómo usar una función, se puede ver su docstring y su firma
    • Se presenta como ejemplo wat / str.split

Exploración de atributos, módulos y código fuente

  • Para ver el interior del objeto inspeccionado, se pueden listar sus atributos y el tipo de cada atributo
    • Se presenta como ejemplo wat / re.match('(\d)_(.*)', '1_title')
  • También puede usarse para explorar módulos, listando las funciones, clases y submódulos del módulo elegido
    • Hay un ejemplo que ejecuta wat / pathlib después de import pathlib
    • Luego se puede explorar más a fondo con wat / pathlib.fnmatch
  • WAT Inspector oculta por defecto los atributos que empiezan con __
    • Se pueden ver atributos dunder con wat.dunder / {}
  • Para comprobar cómo funciona realmente una función, se puede ver el código fuente
    • Hay un ejemplo que ejecuta wat.code / colorsys.hsv_to_rgb después de import colorsys
  • Los dict y list anidados se formatean con indentación y de forma legible

Sesiones de debugging e inspección de variables

  • Después de iniciar el debugger interactivo de Python con breakpoint(), se pueden inspeccionar objetos ahí mismo
  • El ejemplo con Pdb inspecciona variables locales con wat / foo después de import wat o de pegar el snippet de Insta-Load, y continúa la ejecución con c
  • Las variables locales y globales pueden revisarse con wat.locals y wat.globals, respectivamente
  • Si se llama wat() sin argumentos, imprime las variables locales del stack del llamador bajo el título Local variables

Ejemplos para aprender los internos de Python

  • Incluye ejemplos para aprender y entender el comportamiento interno de Python
  • reversed([]) == reversed([]) es False, y wat.s / reversed([]) muestra que el valor es un objeto list_reverseiterator y que el tipo es list_reverseiterator
  • wat / type('ObjectCreator', (), {}) muestra el valor de una clase creada dinámicamente, el tipo type y signature: class ObjectCreator()
  • wat / type muestra el valor del propio type, el tipo type, la firma class type(…), la documentación type(object) -> the object's type y type(name, bases, dict, **kwds) -> a new type, y atributos públicos como mro
  • wat.s / List[str] muestra el valor typing.List[str], el tipo typing._GenericAlias, los tipos padre typing._BaseGenericAlias y typing._Final, y la firma def List(*args, **kwargs)
  • wat(str | None) muestra el valor str | None y el tipo types.UnionType
  • Como ejemplos de exploración de objetos integrados de Python, se presentan wat / __builtins__ y wat / ...
  • También se puede inspeccionar el propio WAT
    • Se presentan como ejemplos wat.dunder / wat y wat.code / wat.__truediv__

Resumen del funcionamiento interno

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False) construye como cadena el resultado de la inspección del objeto
    • Si all=True, se activan también dunder, long, code y caller
    • Si public=True, se desactiva la salida de privados
    • Si sys.stdout.isatty() es verdadero, obtiene el ancho de la terminal y agrega separadores arriba y abajo de la salida
  • La salida de la inspección se genera en este orden: valor del objeto, representación como cadena, tipo, tipos padre, longitud, firma, documentación, código fuente y sección de atributos
  • La inspección de atributos recorre dir(obj) en orden por nombre
    • Los atributos dunder se excluyen si la opción dunder está desactivada
    • Los atributos privados que empiezan con _ se excluyen si la configuración de privados está desactivada
    • Si getattr(obj, key) lanza una BaseException, se usa el objeto de la excepción como valor
  • Para objetos callable, formatea la firma con base en inspect.signature(obj)
    • Si falla, devuelve una firma alternativa con la forma (...)
    • Para clases agrega el prefijo class ; para coroutine functions, async def ; y para funciones, métodos, builtins u objetos con __name__, el prefijo def
  • Si code=True y el objeto es una clase o callable, imprime el código fuente con inspect.getsource(obj)
    • Si ocurre OSError, TypeError o IndentationError, devuelve un mensaje de fallo
  • Los formateadores de dict y list devuelven ERROR: too deeply nested si la profundidad de indentación supera 30

Salida con color y temas

  • La salida con color puede controlarse mediante variables de entorno
    • WAT_COLOR="false" desactiva la salida con color en la consola
    • WAT_COLOR="true" fuerza la salida con color incluso en entornos non-tty
  • La variable de entorno WAT_COLORS permite personalizar el tema de colores
  • El tema por defecto es un mapeo de códigos de color ANSI con la forma 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) elimina las secuencias de escape ANSI usando una expresión regular

Inspiración

1 comentarios

 
GN⁺ 2024-07-26
Opiniones de Hacker News
  • Wow, está muy bueno. Antes usaba python-ls[0] para algo parecido, pero por alguna razón que no recuerdo algo se rompió y ya no recibe mantenimiento.
    Pienso sumarlo a mi caja de herramientas de depuración, que consiste principalmente en snoop[1] y pdbpp. Lo que me gustaría para wat es algo como un widget de ipy que facilite explorar objetos en Jupyter.
    También me gusta el hack de exec con base64. Llevo mucho tiempo usando Python y hasta ahora no se me había ocurrido ni lo había visto, así que seguro lo voy a probar para varias cosas.
    [0] https://github.com/gabrielcnr/python-ls
    [1] https://pypi.org/project/snoop/

  • Se ve interesante. En Python uso dir todo el tiempo, y cuando la documentación no es muy buena, a veces resulta más útil que la documentación oficial.
    El shell interactivo es una de las verdaderas fortalezas de Python, y me sorprende que no haya más herramientas nuevas o innovación alrededor de eso.

    • También está la función help(). Es realmente útil.
  • Parece una versión más vistosa del viejo icecream.
    https://github.com/gruns/icecream
    Si no lo conoces, también puedes ver más abajo la lista de implementaciones para otros lenguajes.
    https://github.com/gruns/icecream#icecream-in-other-language...

  • Este tipo de herramientas es útil.
    Hace 20 años hice un introspector de objetos para Zope.
    Hoy uso devtools todos los días, e icecream y q de vez en cuando. También voy a probar wat.

  • from wat import wat
    Con lo genial que es el carácter del proyecto, me sorprende que no ofrezca simplemente import wat con la misma sintaxis de uso. Así incluso podría haber hecho que los usuarios curiosos descubrieran el truco probando wat/wat.

    • import wat estaría bien, pero en Python existe la limitación de que no se puede hacer invocable a un módulo. Por eso se terminó usando el más largo from wat import wat.
      No estoy del todo seguro, pero import wat; wat.wat / object quizá sería más cómodo.
  • Se ve muy útil, pero me pregunto si soy el único al que le molesta la tendencia reciente de sobrecargar operadores totalmente ajenos en nombre de la legibilidad, en este caso el operador /.

    • En este caso estoy de acuerdo en que sobrecargar / es una elección rara. Aun así, es una lástima que no se pueda sobrecargar is. Siendo realistas, wat(foo) habría sido suficiente.
  • Para evitar imports engorrosos, también puedes agregar esto al archivo $PYTHONSTARTUP:
    try:
    from wat import wat
    except ImportError:
    pass

    • Incluso se podría agregar un importador inline en base64 bastante elegante.
      Al final imprimí esa salida y la dejé en un directorio apuntado por PYTHONPATH para tenerla siempre disponible.
      Habrá que ver si lo sigo usando.
  • Wow, si hubiera tenido una herramienta así cuando aprendía Python, creo que habría cambiado las reglas del juego. Cuando aprendes un lenguaje, ver qué pasa por dentro es una ruta clave, y la depuración básica de Python es decepcionante, siendo generosos.
    En su lugar instalé pry y me volví un fan entusiasta de Ruby, pero esta herramienta quizá podría hacer que vuelva a intentar con Python.

  • El autor usa internamente el módulo inspect de Python de la biblioteca estándar para ofrecer la funcionalidad. Claro que agregó mucho valor encima de eso.
    Vean inspection.py del módulo wat.
    En la línea 2 dice:
    import inspect as std_inspect

  • “Si quieres depurar algo rápido, puedes usar este inspector en la misma sesión sin instalar nada”.
    “Pega este snippet en el intérprete de Python para cargarlo al instante”.
    La idea de poner en el README del proyecto una copia completa del proyecto como datos comprimidos codificados en base64 es bastante ingeniosa.
    Encaja especialmente bien para un proyecto de este tipo, que quizá no se te ocurrió preinstalar en el entorno donde justo terminarás necesitándolo.