1 pontos por GN⁺ 2024-08-23 | 1 comentários | Compartilhar no WhatsApp

O pré-processador do Python

  • A afirmação de que Python não tem pré-processador não é verdadeira
  • Python tem um pré-processador muito poderoso

Codificação do código-fonte em Python

  • Graças à PEP-0263, é possível definir a codificação do código-fonte
  • A codificação pode ser configurada adicionando um comentário mágico nas duas primeiras linhas
  • Exemplos: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :

Arquivos de configuração de caminho (.pth)

  • Se o interpretador Python iniciar sem a opção -S, ele carrega automaticamente o pacote site
  • É possível expandir o caminho de busca de módulos adicionando arquivos .pth na pasta site-packages
  • Linhas em arquivos .pth que começam com import são executadas
  • Com isso, é possível executar código arbitrário durante a inicialização do interpretador Python

Definição de codecs personalizados

  • O interpretador Python precisa de duas coisas para aceitar isso:
    • função decode(data: bytes) -> tuple[str, int]
    • classe de decodificador incremental
  • Use codecs.utf_8_decode para fazer a decodificação real e depois passe a string resultante ao pré-processador
  • É recomendável capturar exceções, imprimi-las e lançá-las novamente

Fornecendo um decodificador incremental

  • Implemente o decodificador incremental herdando de codecs.BufferedIncrementalDecoder
  • Colete os dados no buffer e pré-processe o arquivo inteiro na chamada final de decodificação

Estendendo o Python

  • É relativamente fácil estender o Python usando a biblioteca padrão da linguagem
  • É possível usar o módulo tokenize para modificar o fluxo de tokens do arquivo ou o módulo ast para modificar a árvore de sintaxe abstrata
Incremento e decremento unários
  • Python não possui operadores unários de incremento e decremento
  • x++, x-- não são válidos
  • ++x, --x são válidos, mas têm outro significado
  • É possível converter expressões de incremento e decremento unários em expressões Python
Exemplo
  • Arquivo de entrada 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")
    
  • Arquivo convertido:
    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 com chaves (Bython)

  • É possível usar chaves para delimitar escopos em vez de indentação no Python
  • Use tokenize.generate_tokens para modificar o fluxo de tokens
Exemplo
  • Arquivo de entrada 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()})
    }
    
  • Arquivo convertido:
    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()})
    

Interpretando outras linguagens

  • É possível fazer o interpretador Python interpretar outras linguagens
  • Exemplos: C, C++, TOML
Exemplo
  • Arquivo C++ test.cpp:
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    
  • Arquivo convertido:
    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()
    

Validação de dados

  • É possível validar dados em formato TOML usando JSON Schema
  • Use jsonschema para realizar a validação de fato
Exemplo
  • Arquivo de schema 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"]
        }
      }
    }
    
  • Arquivo de dados válido data_valid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, 20, 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    
  • Arquivo de dados inválido data_invalid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, "20", 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    

Conclusão

  • Com codecs personalizados e arquivos de configuração de caminho, é possível alterar bastante o comportamento do interpretador Python
  • Alguns exemplos são pythonql, future-typing, future-fstrings, future-annotations
  • É possível usar magic_codec para experimentar facilmente seu próprio pré-processador

Resumo do GN⁺

  • É possível usar o pré-processador do Python para realizar várias extensões de linguagem e validação de dados
  • O texto explica como alterar o comportamento do interpretador Python por meio de codecs personalizados
  • Este artigo oferece ferramentas e técnicas úteis para desenvolvedores Python
  • Projetos com funcionalidades semelhantes incluem pythonql e future-typing

1 comentários

 
GN⁺ 2024-08-23
Comentários no Hacker News
  • A mensagem de erro de sintaxe de from __future__ import braces está hardcoded no cpython desde 2001

    • Foi escrita por Jeremy Hylton, que atualmente trabalha no Google como engenheiro sênior responsável pela qualidade de busca com IA
    • Surpreende ver como, ao longo de 24 anos, a carreira de uma pessoa evoluiu de comemorar a proibição de uma sintaxe específica para trabalhar em sistemas de busca que não precisam de sintaxe dedicada
  • Pensei em formas criativas de demissão usando import-hooks, mas foi uma pena que o regex de codec impeça usar algo como μtf8

    • Teria que usar import hooks, preprocessadores e sys.settrace para fazer monkey patch de todas as funções para a função chamada anteriormente, além de trocar stdout e stderr a cada 17 minutos
  • Existe um motivo para o Python não expor hooks de preprocessador, e acho que adultos razoáveis deveriam evitar isso

    • Mas quero buscar diversão independentemente do que adultos razoáveis pensam
  • Um preprocessador é mais prático e útil

    • Já fiz hacks reescrevendo código com o módulo ast, executando com exec e depois inserindo exit()
    • Antes de todos os dicts passarem a ser ordenados, eu usava bastante a funcionalidade de reescrita de ast de forma útil
  • Adoro a flexibilidade do Python

    • A coisa mais amaldiçoada que já fiz foi modificar strings in-place; abusei de mmap para fazer um script se modificar sozinho
    • Agora quero escrever um interpretador Lisp
  • O melhor caso de uso com pyxl veio da inspiração no jsx

    • Dá para escrever código HTML usando # coding: pyxl
  • Fico me perguntando se a transição do Python 2 para o 3 poderia ter sido tratada melhor

    • # coding: six.python2 poderia tornar código Python2 válido em Python3, e # coding: six.python3 poderia ajustar código Python3 para rodar em Python2
  • Fico feliz que tenham gostado da ideia; em breve deve sair mais conteúdo

  • Faz tempo que eu não me surpreendia com uma ideia completamente nova

  • Se você quer geração de código inline em Python, pode usar o cog do Ned Batchelder

  • Fico pensando se dependências introduzidas por essa estratégia de hooks de codificação seriam detectadas por pip freeze ou pelo uv

    • Se não forem, então divirta-se. Pode ser mais fácil reescrever a biblioteca (se existe essa armadilha, provavelmente há outras também)