1 points par GN⁺ 2024-08-23 | 1 commentaires | Partager sur WhatsApp

Le préprocesseur de Python

  • Affirmer que Python n’a pas de préprocesseur est faux
  • Python dispose d’un préprocesseur très puissant

Encodage du code source Python

  • Grâce à PEP-0263, il est possible de définir l’encodage du code source
  • On peut définir l’encodage en ajoutant un commentaire magique dans les deux premières lignes
  • Exemples : # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :

Fichiers de configuration de chemin (.pth)

  • Si l’interpréteur Python démarre sans l’option -S, il charge automatiquement le package site
  • On peut étendre le chemin de recherche des modules en ajoutant un fichier .pth dans le dossier site-packages
  • Les lignes d’un fichier .pth qui commencent par import sont exécutées
  • Cela permet d’exécuter du code arbitraire lors de l’initialisation de l’interpréteur Python

Définir des codecs personnalisés

  • L’interpréteur Python attend deux éléments :
    • une fonction decode(data: bytes) -> tuple[str, int]
    • une classe de décodeur incrémental
  • Utiliser codecs.utf_8_decode pour effectuer le décodage réel, puis transmettre la chaîne résultante au préprocesseur
  • Il est recommandé d’attraper les exceptions, de les afficher, puis de les relancer

Fournir un décodeur incrémental

  • Implémenter un décodeur incrémental en héritant de codecs.BufferedIncrementalDecoder
  • Accumuler les données dans un tampon, puis prétraiter l’ensemble du fichier lors de l’appel final de décodage

Étendre Python

  • Étendre Python à l’aide de sa bibliothèque standard est relativement simple
  • On peut modifier le flux de tokens d’un fichier avec le module tokenize, ou modifier l’arbre syntaxique abstrait avec le module ast
Incrémentation et décrémentation unaires
  • Python n’a pas d’opérateurs d’incrémentation et de décrémentation unaires
  • x++, x-- ne sont pas valides
  • ++x, --x sont valides mais ont un autre sens
  • On peut convertir des expressions d’incrémentation et de décrémentation unaires en expressions Python
Exemple
  • Fichier d’entrée 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")
    
  • Fichier transformé :
    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 avec des accolades (Bython)

  • Il est possible d’utiliser des accolades au lieu de l’indentation pour délimiter les blocs en Python
  • Utilisation de tokenize.generate_tokens pour modifier le flux de tokens
Exemple
  • Fichier d’entrée 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()})
    }
    
  • Fichier transformé :
    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()})
    

Interpréter d’autres langages

  • On peut faire en sorte que l’interpréteur Python interprète d’autres langages
  • Exemples : C, C++, TOML
Exemple
  • Fichier C++ test.cpp :
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    
  • Fichier transformé :
    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()
    

Validation de données

  • Des données au format TOML peuvent être validées à l’aide d’un schéma JSON
  • jsonschema est utilisé pour effectuer la validation réelle
Exemple
  • Fichier de schéma 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"]
        }
      }
    }
    
  • Fichier de données valide data_valid.toml :
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, 20, 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    
  • Fichier de données invalide data_invalid.toml :
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, "20", 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    

Conclusion

  • En utilisant des codecs personnalisés et des fichiers de configuration de chemin, on peut modifier en profondeur le comportement de l’interpréteur Python
  • Parmi les exemples, on trouve pythonql, future-typing, future-fstrings, future-annotations
  • magic_codec permet d’expérimenter facilement son propre préprocesseur

Résumé de GN⁺

  • Le préprocesseur de Python permet de réaliser diverses extensions du langage et de la validation de données
  • L’article explique comment modifier le comportement de l’interpréteur Python via des codecs personnalisés
  • Cet article fournit aux développeurs Python des outils et des techniques utiles
  • Des projets offrant des fonctionnalités similaires incluent pythonql, future-typing, etc.

1 commentaires

 
GN⁺ 2024-08-23
Commentaires Hacker News
  • Le message d’erreur de syntaxe pour from __future__ import braces est codé en dur dans cpython depuis 2001

    • Il a été écrit par Jeremy Hylton, qui travaille aujourd’hui chez Google comme ingénieur principal chargé de la qualité de la recherche IA
    • Il est étonnant de voir qu’en 24 ans, la carrière d’une personne est passée de la commémoration d’une interdiction syntaxique spécifique au travail sur un système de recherche ne nécessitant aucune syntaxe dédiée
  • J’ai pensé à une méthode créative de licenciement en utilisant des import-hooks, mais c’est dommage que la regex des codecs empêche d’utiliser quelque chose comme "μtf8"

    • Il faut donc utiliser des import hooks, des preprocessors et sys.settrace pour monkey-patcher toutes les fonctions vers la fonction appelée précédemment, et remplacer stdout et stderr toutes les 17 minutes
  • Il y a une raison pour laquelle Python n’expose pas de hook de préprocesseur, et je pense qu’un adulte raisonnable devrait éviter ça

    • Mais indépendamment des adultes raisonnables, j’ai envie de viser l’amusement
  • Le préprocesseur est plus pratique et plus utile

    • J’ai bricolé un système qui réécrit le code avec le module ast, l’exécute avec exec, puis insère exit()
    • Avant que tous les dicts ne soient ordonnés, j’utilisais utilement la réécriture ast
  • J’adore la flexibilité de Python

    • Le travail le plus maudit que j’ai fait consistait à modifier des chaînes sur place, en abusant de mmap pour faire en sorte que le script se modifie lui-même
    • Maintenant, j’ai envie d’écrire un interpréteur Lisp
  • Le meilleur cas d’usage de pyxl s’inspire de jsx

    • Avec # coding: pyxl, on peut écrire du code HTML
  • Je me demande s’il aurait été possible de mieux gérer la transition de Python 2 vers 3

    • # coding: six.python2 pourrait rendre du code Python2 valide en Python3, et # coding: six.python3 pourrait adapter du code Python3 pour qu’il s’exécute en Python2
  • Je suis heureux que cette idée vous plaise, il y en aura bientôt davantage

  • Cela faisait longtemps que je n’avais pas été surpris par une idée totalement nouvelle

  • Si vous voulez de la génération de code inline en Python, vous pouvez utiliser cog de Ned Batchelder

  • Je me demande si les dépendances introduites par cette stratégie de hook de codage sont détectées par pip freeze ou uv

    • Sinon, amusez-vous bien avec ça. Il sera peut-être plus simple de réécrire la bibliothèque (s’il y a ce genre de piège, il y a probablement aussi d’autres pièges)