1 puntos por GN⁺ 2024-08-23 | 1 comentarios | Compartir por WhatsApp

El preprocesador de Python

  • La afirmación de que Python no tiene preprocesador no es cierta
  • Python sí tiene un preprocesador muy potente

Codificación del código fuente en Python

  • Gracias a PEP-0263, se puede definir la codificación del código fuente
  • Se puede configurar la codificación agregando un comentario mágico en las primeras dos líneas
  • Ejemplos: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :

Archivos de configuración de ruta (.pth)

  • Si el intérprete de Python se inicia sin la opción -S, carga automáticamente el paquete site
  • Se puede ampliar la ruta de búsqueda de módulos agregando archivos .pth en la carpeta site-packages
  • Las líneas de los archivos .pth que comienzan con import se ejecutan
  • Esto permite ejecutar código arbitrario durante la inicialización del intérprete de Python

Definición de códecs personalizados

  • El intérprete de Python necesita dos cosas para quedar satisfecho:
    • una función decode(data: bytes) -> tuple[str, int]
    • una clase de decodificador incremental
  • Se usa codecs.utf_8_decode para realizar la decodificación real y luego pasar la cadena resultante al preprocesador
  • Es recomendable capturar las excepciones, mostrarlas y volver a lanzarlas

Proveer un decodificador incremental

  • Se implementa un decodificador incremental heredando de codecs.BufferedIncrementalDecoder
  • Los datos se acumulan en el búfer y, en la llamada final de decodificación, se preprocesa el archivo completo

Extender Python

  • Extender Python usando su biblioteca estándar es relativamente sencillo
  • Se puede modificar el flujo de tokens de un archivo con el módulo tokenize, o modificar el árbol de sintaxis abstracta con el módulo ast
Incremento y decremento unarios
  • Python no tiene operadores unarios de incremento y decremento
  • x++, x-- no son válidos
  • ++x, --x sí son válidos, pero tienen otro significado
  • Las expresiones de incremento y decremento unario pueden convertirse en expresiones de Python
Ejemplo
  • Archivo 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")
    
  • Archivo transformado:
    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 con llaves (Bython)

  • Se puede delimitar el alcance con llaves en lugar de la indentación de Python
  • Se usa tokenize.generate_tokens para modificar el flujo de tokens
Ejemplo
  • Archivo 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()})
    }
    
  • Archivo transformado:
    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()})
    

Interpretar otros lenguajes

  • Se puede hacer que el intérprete de Python interprete otros lenguajes
  • Ejemplos: C, C++, TOML
Ejemplo
  • Archivo C++ test.cpp:
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    
  • Archivo transformado:
    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()
    

Validación de datos

  • Se pueden validar datos en formato TOML usando un esquema JSON
  • Se usa jsonschema para realizar la validación real
Ejemplo
  • Archivo de esquema 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"]
        }
      }
    }
    
  • Archivo de datos 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
    
  • Archivo de datos no válido data_invalid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, "20", 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    

Conclusión

  • Se puede cambiar enormemente el comportamiento del intérprete de Python usando códecs personalizados y archivos de configuración de ruta
  • Algunos ejemplos son pythonql, future-typing, future-fstrings, future-annotations
  • Se puede experimentar fácilmente con tu propio preprocesador usando magic_codec

Resumen de GN⁺

  • Se puede aprovechar el preprocesador de Python para realizar varias extensiones de lenguaje y validación de datos
  • Se explica cómo cambiar el comportamiento del intérprete de Python mediante códecs personalizados
  • Este artículo ofrece herramientas y técnicas útiles para desarrolladores de Python
  • Proyectos con funciones similares incluyen pythonql, future-typing, entre otros

1 comentarios

 
GN⁺ 2024-08-23
Comentarios de Hacker News
  • El mensaje de error de sintaxis de from __future__ import braces está hardcodeado en cpython desde 2001

    • Lo escribió Jeremy Hylton, y actualmente trabaja en Google como ingeniero principal a cargo de la calidad de búsqueda con IA
    • Sorprende que, en 24 años, la carrera de una persona haya evolucionado desde conmemorar una prohibición sintáctica específica hasta trabajar en sistemas de búsqueda que no requieren sintaxis dedicada
  • Pensé en formas creativas de despido usando import-hooks, pero fue una lástima que la regex de codec impidiera usar algo como μtf8

    • Habría que usar import hooks, preprocessors y sys.settrace para hacer monkey patch de todas las funciones con la función llamada previamente, y reemplazar stdout y stderr cada 17 minutos
  • Hay una razón por la que Python no expone hooks de preprocesador, y creo que cualquier adulto razonable debería evitarlos

    • Pero, independientemente de los adultos razonables, quiero perseguir la diversión
  • El preprocesador es más cómodo y útil

    • Hice hacks reescribiendo código con el módulo ast, ejecutándolo con exec y luego insertando exit()
    • Antes de que todos los dicts conservaran el orden, usé de forma útil la reescritura de ast
  • Me encanta la flexibilidad de Python

    • La tarea más maldita que hice fue mutar cadenas en el lugar; abusé de mmap para hacer que el script se modificara a sí mismo
    • Ahora quiero escribir un intérprete de Lisp
  • El mejor caso de uso de pyxl está inspirado en jsx

    • Puedes escribir código HTML usando # coding: pyxl
  • Me pregunto si la transición de Python 2 a 3 podría haberse manejado mejor

    • # coding: six.python2 puede hacer que código de Python2 sea válido en Python3, y # coding: six.python3 puede ajustar código de Python3 para que pueda ejecutarse en Python2
  • Me alegra que les guste esta idea; pronto habrá más

  • Después de mucho tiempo, me sorprendió una idea completamente nueva

  • Si quieres generación de código inline en Python, puedes usar cog de Ned Batchelder

  • Me pregunto si las dependencias introducidas con esta estrategia de hooks de codificación son detectadas por pip freeze o uv

    • Si no, que lo disfruten. Reescribir la librería quizá sea más fácil (si hay una trampa como esta, es probable que haya otras)