2 pontos por GN⁺ 2024-07-26 | 1 comentários | Compartilhar no WhatsApp
  • WAT é um inspector para identificar rapidamente o que é um objeto desconhecido no runtime do Python, permitindo ver de uma vez tipo, valor, atributos, métodos, tipos pai, assinatura, documentação e até código-fonte
  • O uso básico é wat / object, funcionando da mesma forma que wat(object), e ele também suporta várias sintaxes como wat.short / 'foo', 'foo' | wat.short, wat('foo', short=True)
  • É possível encadear modifiers como .short, .dunder, .long, .code, .caller, .public, .all, .ret, .str para ajustar o escopo da saída, a forma de retorno, a saída colorida e a exibição do local da chamada
  • A instalação pode ser feita com pip install wat seguido de import wat, e para depuração rápida também é possível colar o snippet Insta-Load na sessão Python para usar no mesmo ambiente sem instalar
  • Exemplos com Django User, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str], str | None mostram que o WAT pode ser usado para debugging, exploração em REPL e aprendizado dos internals do Python

O que o WAT faz

  • WAT é uma ferramenta para explorar e inspecionar objetos Python em runtime
  • Quando fica difícil entender o que é um objeto desconhecido, é possível investigar sua natureza no console Python com o inspector wat
  • Ao executar wat / object sobre um object qualquer, é possível ver as seguintes informações
    • type do objeto
    • valor formatado
    • variáveis e métodos
    • tipos pai
    • assinatura
    • documentação
    • código-fonte
  • A mesma inspeção profunda também pode ser usada com a sintaxe wat(object)
  • Wat é apresentado como uma variação da palavra inglesa what, usada para expressar confusão ou incômodo

Uso básico e sintaxe

  • Para digitação rápida, ele usa o operador de divisão
    • wat / foo é igual a wat(foo)
  • É possível usar várias sintaxes para a mesma inspeção
    • wat.short / 'foo': sintaxe para digitação rápida
    • wat.short('foo')
    • wat('foo', short=True): sintaxe natural de Python
    • 'foo' | wat.short: sintaxe no estilo pipe do Unix
  • É possível ajustar o comportamento da inspeção com a forma wat.modifier / foo
  • Os modifiers podem ser encadeados, por exemplo wat.short.str.gray / 'foo'
  • Como em Python objetos incluem não só estruturas de dados, mas também funções, classes, módulos e tipos embutidos, wat pode explorar qualquer objeto
  • Ao digitar wat no interpretador, é possível ver a ajuda do próprio objeto wat

Ajustando o escopo da inspeção com modifiers

  • .short ou .s oculta atributos internos como variáveis e métodos do objeto, mostrando apenas valor, tipo, tipos pai, assinatura e documentação
  • .dunder mostra atributos dunder que começam com __
  • .long mostra o valor e a docstring sem abreviações
  • .code mostra o código-fonte de funções, métodos e classes
  • .nodocs oculta a documentação de funções e classes
  • .caller mostra como e onde a inspeção foi chamada, funcionando fora do REPL em arquivos
  • .public oculta atributos privados e mostra apenas atributos públicos
  • .all inclui todas as informações possíveis
  • .ret retorna o objeto novamente após a inspeção
  • .str retorna a string de resultado em vez de imprimir
  • .gray desativa a saída colorida no console
  • .color força a saída colorida no console
  • wat.locals inspeciona variáveis locais, e wat.globals inspeciona variáveis globais

Instalação e Insta-Load

  • O fluxo de instalação com pip é o seguinte
    • pip install wat
    • no Python, import wat
  • O pacote wat não tem dependências externas
  • Para debugging rápido, ele oferece o modo Insta-Load, que permite usar na mesma sessão Python sem instalar
  • O Insta-Load funciona colando no interpretador um snippet Python que importa base64 e zlib, restaura uma string de código comprimida e codificada, e a executa com exec(..., globals())
  • Após executar o snippet do Insta-Load, o objeto wat pode ser usado
  • Antes de executar o snippet, recomenda-se validar o conteúdo que será executado
    • É possível verificar previamente o código extraído com print(zlib.decompress(base64.b64decode(code)).decode())
    • Também é possível colar no interpretador o conteúdo de inspection.py para obter o mesmo efeito
    • Outra opção mencionada é instalar o pacote com pip e revisar o código
  • O WAT pode ser carregado a partir de um único glyph Unicode
  • O loader baseado em string Unicode transforma uma longa sequência de emoji e caracteres combinados em bytes com ord(c) & 255, depois executa com zlib.decompress(...) e exec(...)

Entendendo o tipo do objeto e como usá-lo

  • Em Python, uma linguagem de tipagem dinâmica, às vezes é difícil descobrir o tipo de um objeto, e o WAT Inspector mostra o nome do tipo e o módulo de onde ele veio
  • Os exemplos de verificação de tipo mostram valor, tipo e comprimento juntos
    • wat.short / (1,) imprime o valor (1,), o tipo tuple e o comprimento 1
    • wat.short / {None} imprime o valor {None}, o tipo set e o comprimento 1
  • No exemplo com o objeto Django User, wat.short / user mostra str: admin, repr: <User: admin>, o tipo django.contrib.auth.models.User e a lista de tipos pai
  • Depois de confirmar o tipo real, é possível adicionar type annotations no código para reduzir confusões futuras
  • Ao tentar descobrir como usar um objeto desconhecido, é possível imprimir a lista de métodos, a assinatura e a docstring
    • O exemplo dado é wat / ['foo']
    • Para ver a docstring completa, use wat.long
  • Para entender como usar uma função, é possível ver sua docstring e assinatura
    • O exemplo dado é wat / str.split

Explorando atributos, módulos e código-fonte

  • Para inspecionar o interior do objeto-alvo, é possível listar os atributos e o tipo de cada atributo
    • O exemplo dado é wat / re.match('(\\d)_(.*)', '1_title')
  • Também pode ser usado para explorar módulos, listando funções, classes e submódulos do módulo escolhido
    • Há um exemplo com import pathlib seguido de wat / pathlib
    • Depois é possível explorar mais a fundo com wat / pathlib.fnmatch
  • Por padrão, o WAT Inspector oculta atributos que começam com __
    • É possível ver atributos dunder com wat.dunder / {}
  • Para verificar como uma função realmente funciona, é possível ver o código-fonte
    • Há um exemplo com import colorsys seguido de wat.code / colorsys.hsv_to_rgb
  • dict e list aninhados são formatados com indentação legível

Sessões de debugging e inspeção de variáveis

  • É possível iniciar o depurador interativo do Python com breakpoint() e inspecionar objetos naquele ponto
  • No exemplo com Pdb, depois de import wat ou de colar o snippet do Insta-Load, usa-se wat / foo para inspecionar variáveis locais e c para continuar a execução
  • Variáveis locais e globais podem ser vistas com wat.locals e wat.globals, respectivamente
  • Ao chamar wat() sem argumentos, ele imprime as variáveis locais da pilha chamadora sob o título Local variables

Exemplos para aprender os internals do Python

  • O texto inclui exemplos de uso voltados ao aprendizado do funcionamento interno do Python
  • reversed([]) == reversed([]) é False, e wat.s / reversed([]) mostra que o valor é um objeto list_reverseiterator e o tipo também é list_reverseiterator
  • wat / type('ObjectCreator', (), {}) mostra o valor de uma classe criada dinamicamente, o tipo type e signature: class ObjectCreator()
  • wat / type mostra o valor do próprio type, o tipo type, a assinatura class type(…), a documentação type(object) -> the object's type, type(name, bases, dict, **kwds) -> a new type, e atributos públicos como mro
  • wat.s / List[str] mostra o valor typing.List[str], o tipo typing._GenericAlias, os tipos pai typing._BaseGenericAlias, typing._Final, e a assinatura def List(*args, **kwargs)
  • wat(str | None) mostra o valor str | None e o tipo types.UnionType
  • Como exemplos de exploração de objetos embutidos do Python, são citados wat / __builtins__ e wat / ...
  • Também é possível inspecionar o próprio WAT
    • Os exemplos dados são wat.dunder / wat e wat.code / wat.__truediv__

Resumo do funcionamento interno

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False) monta em string o resultado da inspeção do objeto
    • Quando all=True, dunder, long, code e caller são ativados juntos
    • Quando public=True, a saída de membros privados é desativada
    • Se sys.stdout.isatty() for verdadeiro, ele obtém a largura do terminal e adiciona separadores acima e abaixo da saída
  • A saída da inspeção é gerada na ordem: valor do objeto, representação em string, tipo, tipos pai, comprimento, assinatura, documentação, código-fonte e seção de atributos
  • A inspeção de atributos percorre dir(obj) em ordem alfabética dos nomes
    • Atributos dunder são excluídos se a opção dunder estiver desativada
    • Atributos privados que começam com _ são excluídos se a configuração privada estiver desativada
    • Se getattr(obj, key) levantar BaseException, o valor usado passa a ser o próprio objeto de exceção
  • Objetos callable têm a assinatura formatada com base em inspect.signature(obj)
    • Se falhar, é retornada uma assinatura alternativa no formato (...)
    • Classes recebem o prefixo class , coroutine functions recebem async def , e funções, métodos, builtins e objetos com __name__ recebem o prefixo def
  • Quando code=True e o objeto é uma classe ou callable, o código-fonte é impresso com inspect.getsource(obj)
    • Em caso de OSError, TypeError ou IndentationError, é retornada uma mensagem de falha
  • Os formatadores de dict e list retornam ERROR: too deeply nested quando a profundidade de indentação ultrapassa 30

Saída colorida e temas

  • É possível controlar a saída colorida com variáveis de ambiente
    • WAT_COLOR="false" desativa a saída colorida no console
    • WAT_COLOR="true" força cores mesmo em ambientes non-tty
  • A variável de ambiente WAT_COLORS permite customizar o tema de cores
  • O tema padrão é um mapeamento de códigos ANSI no formato 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) remove sequências de escape ANSI usando expressão regular

Inspiração

1 comentários

 
GN⁺ 2024-07-26
Opiniões no Hacker News
  • Uau, muito bom. Eu usava o python-ls[0] para um propósito parecido no passado, mas algo quebrou por algum motivo de que não me lembro, e ele também não é mais mantido
    Pretendo adicioná-lo à minha caixa de ferramentas de depuração, composta principalmente por snoop[1] e pdbpp. O que eu gostaria no wat é algo como um widget ipy que facilite explorar objetos no Jupyter
    Também gostei do hack de exec com base64. Uso Python há muito tempo e, ainda assim, nunca tinha pensado nisso nem visto algo assim até agora, então com certeza vou experimentar para alguns usos
    [0] https://github.com/gabrielcnr/python-ls
    [1] https://pypi.org/project/snoop/

  • Parece interessante. Uso dir o tempo todo em Python e, quando a documentação é fraca, às vezes ele é mais útil do que a documentação oficial
    O shell interativo é um dos verdadeiros pontos fortes do Python, então é surpreendente que não haja mais ferramentas novas ou inovações desse tipo ao redor dele

    • Também existe a função help(). Ela é realmente útil
  • Parece uma versão mais vistosa do antigo icecream
    https://github.com/gruns/icecream
    Se você não conhece, também vale ver mais abaixo a lista de implementações para outras linguagens
    https://github.com/gruns/icecream#icecream-in-other-language...

  • Ferramentas desse tipo são úteis
    Há 20 anos, eu criei um introspector de objetos para Zope
    Hoje em dia uso devtools diariamente, e icecream e q de vez em quando. Também vou experimentar o wat

  • from wat import wat
    Com um projeto de caráter tão legal, surpreende que ele não ofereça simplesmente import wat com a mesma sintaxe de uso. Assim, usuários curiosos poderiam acabar descobrindo o truque ao tentar wat/wat

    • Seria bom se fosse import wat, mas no Python existe a limitação de não poder tornar um módulo chamável. Por isso acabou ficando o mais longo from wat import wat
      Não tenho certeza, mas import wat; wat.wat / object talvez fosse mais conveniente
  • Parece muito útil, mas fico me perguntando se sou o único incomodado com a tendência recente de sobrecarregar operadores totalmente não relacionados, neste caso o operador /, em nome da legibilidade

    • Concordo que, neste caso, sobrecarregar / é uma escolha estranha. Ainda assim, é uma pena que não seja possível sobrecarregar is. Na prática, wat(foo) provavelmente já teria sido suficiente
  • Para evitar imports trabalhosos, você também pode adicionar isto ao arquivo $PYTHONSTARTUP
    try:
    from wat import wat
    except ImportError:
    pass

    • Dá até para adicionar um importador inline em base64 bem bacana
      Acabei imprimindo a saída dele e colocando-a em um diretório apontado por PYTHONPATH, para poder usar sempre
      Vamos ver se vou continuar usando
  • Uau, se eu tivesse tido uma ferramenta dessas quando estava aprendendo Python, acho que teria mudado o jogo. Ao aprender uma linguagem, enxergar o que acontece por dentro é o caminho essencial, e a depuração padrão do Python é decepcionante, para dizer o mínimo
    Em vez disso, instalei o pry e virei um fã entusiasmado de Ruby, mas uma ferramenta dessas talvez me faça tentar Python de novo

  • O autor usa internamente o módulo inspect do Python da biblioteca padrão para fornecer a funcionalidade. Claro, acrescentou bastante valor em cima disso
    Veja inspection.py no módulo wat
    Na segunda linha está assim:
    import inspect as std_inspect

  • “Se você quiser depurar algo rapidamente, pode usar este inspetor na mesma sessão sem instalar nada”
    “Cole este snippet no interpretador Python para carregá-lo na hora”
    A ideia de colocar no README do projeto uma cópia inteira do projeto como dados compactados codificados em base64 é bem engenhosa
    Combina especialmente bem com esse tipo de projeto, que talvez você não tenha pensado em preparar com antecedência justamente nos ambientes em que acabará precisando dele