
Écrire du code C sans apprendre le C : la magie de Python
l’autre jour, une bibliothèque intéressante dont je n’avais jamais entendu parler auparavant.
PythonC est un compilateur DSL (Domain-Specific Language) qui permet aux développeurs d’écrire des programmes C en utilisant la syntaxe Python standard. Il prend un sous-ensemble de code Python typé statiquement et le compile directement en code machine natif via LLVM IR (Low Level Virtual Machine Intermediate Representation).
LLVM IR est un format de code indépendant de la plate-forme utilisé en interne par le framework du compilateur LLVM. Les compilateurs traduisent d’abord le code source en LLVM IR, puis LLVM transforme cet IR en code machine optimisé pour des processeurs spécifiques (x86, ARM, etc.).
Une philosophie de conception fondamentale de PythonC est la suivante : Runtime équivalent C + temps de compilation alimenté par Python, et il présente les arguments de vente presque uniques suivants.
1. Crée des exécutables natifs autonomes
Contrairement aux outils tels que Cython, qui sont principalement utilisés pour créer des extensions C afin d’accélérer les scripts Python existants, PythoC peut générer des exécutables de style C complètement indépendants et autonomes. Une fois compilé, le binaire résultant ne nécessite pas l’interpréteur Python ou un garbage collector pour s’exécuter.
2. Possède un contrôle de bas niveau avec la syntaxe Python
Python C reflète les capacités du C mais les enveloppe dans la syntaxe plus propre de Python. Pour y parvenir, il utilise des indices de type natifs de la machine au lieu des types dynamiques standard de Python.
- Primitives: i32, i8, f64, etc.
- Structures de mémoire : Pointeurs (ptr[T]), tableaux (tableau[T, N]) et les structures (créées en décorant les classes Python standard).
- Gestion manuelle de la mémoire : Parce qu’il n’utilise pas de garbage collector par défaut, la gestion de la mémoire est explicite, tout comme en C. Cependant, il propose des contrôles de sécurité modernes et facultatifs, tels que types linéaires (qui garantissent que chaque allocation est explicitement désallouée pour éviter les fuites) et types de raffinement (pour appliquer les contrôles de validation au moment de la compilation).
Python comme moteur de métaprogrammation
L’une des fonctionnalités les plus puissantes de PythonC est la gestion de l’étape de compilation. Étant donné que l’environnement de compilation est uniquement Python, vous pouvez utiliser la logique Python standard pour générer, manipuler et spécialiser votre code Python. avant il est compilé dans LLVM. Cela vous offre des capacités de génération de code très flexibles au moment de la compilation (similaires aux modèles C++ mais pilotées par Python pur).
Cela semble prometteur, mais la réalité est-elle à la hauteur du battage médiatique ? Ok, voyons cette bibliothèque en action. L’installer est simple, comme la plupart des bibliothèques Python, c’est juste une installation pip comme celle-ci :
pip install pythoc
Mais il est probablement préférable de mettre en place un environnement de développement approprié dans lequel vous pouvez cloisonner vos différents projets. Dans mon exemple, j’utilise l’utilitaire UV, mais utilisez la méthode avec laquelle vous êtes le plus à l’aise. Tapez les commandes suivantes dans votre terminal de ligne de commande.
C:\Users\thoma\projects> cd projects
C:\Users\thoma\projects> uv init pythoc_test
C:\Users\thoma\projects> cd pythoc_test
C:\Users\thoma\projects\pythoc_test> uv venv --python 3.12
C:\Users\thoma\projects\pythoc_test> .venv\Scripts\activate
(pythoc_test) C:\Users\thoma\projects\pythoc_test> uv pip install pythoc
Un exemple simple
Pour utiliser PythoC, vous définissez des fonctions en utilisant des types de machines spécifiques et les marquez avec la compilation de PythoC décorateur. Il existe deux manières principales d’exécuter votre code Python. Vous pouvez appeler la bibliothèque compilée directement depuis Python comme ceci,
from pythoc import compile, i32
@compile
def add(x: i32, y: i32) -> i32:
return x + y
# Can compile to native code
@compile
def main() -> i32:
return add(10, 20)
# Call the compiled dynamic library from Python directly
result = main()
print(result)
Ensuite, exécutez-le comme ça.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test1.py
30
Ou vous pouvez créer un exécutable autonome que vous pouvez exécuter indépendamment de Python. Pour ce faire, utilisez un code comme celui-ci.
from pythoc import compile, i32
@compile
def add(x: i32, y: i32) -> i32:
print(x + y)
return x + y
# Can compile to native code
@compile
def main() -> i32:
return add(10, 20)
if __name__ == "__main__":
from pythoc import compile_to_executable
compile_to_executable()
Nous le gérons de la même manière.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test4.py
Successfully compiled to executable: build\test4.exe
Linked 1 object file(s)
Cette fois, nous ne voyons aucun résultat. Au lieu de cela, PythoC crée un répertoire de construction sous votre répertoire actuel, puis y crée un fichier exécutable que vous pouvez exécuter.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>dir build\test4*
Volume in drive C is Windows
Volume Serial Number is EEB4-E9CA
Directory of C:\Users\thoma\projects\pythoc_test\build
26/02/2026 14:32 297 test4.deps
26/02/2026 14:32 168,448 test4.exe
26/02/2026 14:32 633 test4.ll
26/02/2026 14:32 412 test4.o
26/02/2026 14:32 0 test4.o.lock
26/02/2026 14:32 1,105,920 test4.pdb
Nous pouvons exécuter le fichier test4.exe comme n’importe quel autre exécutable.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>build\test4.exe
(pythoc_test) C:\Users\thoma\projects\pythoc_test>
Mais attendez une seconde. Dans notre code Python, nous avons explicitement demandé d’imprimer le résultat de l’addition, mais nous ne voyons aucune sortie. Que se passe-t-il?
La réponse est que la fonction Python print() intégrée s’appuie sur l’interpréteur Python exécuté en arrière-plan pour comprendre comment afficher les objets. Parce que PythoC supprime tout cela pour créer un petit exécutable natif ultra-rapide, l’instruction print est supprimée.
Pour imprimer à l’écran dans un binaire natif, vous devez utiliser la fonction standard de la bibliothèque C : printf.
Comment utiliser printf dans PythonC
En C (et donc en PythonC), l’impression de variables nécessite des spécificateurs de format. Vous écrivez une chaîne avec un espace réservé (comme %d pour un entier décimal), puis transmettez la variable que vous souhaitez insérer dans cet espace réservé.
Voici comment mettre à jour notre code pour importer la fonction C printf et l’utiliser correctement :
from pythoc import compile, i32, ptr, i8, extern
# 1. Tell PythoC to link to the standard C printf function
@extern
def printf(fmt: ptr[i8], *args) -> i32:
pass
@compile
def add(x: i32, y: i32) -> i32:
printf("Adding 10 and 20 = %d\n", x+y)
return x + y
@compile
def main() -> i32:
result = add(10, 20)
# 2. Use printf with a C-style format string.
# %d is the placeholder for our integer (result).
# \n adds a new line at the end.
return 0
if __name__ == "__main__":
from pythoc import compile_to_executable
compile_to_executable()
Maintenant, si nous réexécutons le code ci-dessus et exécutons l’exécutable résultant, notre sortie devient ce à quoi nous nous attendions.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test5.py
Successfully compiled to executable: build\test5.exe
Linked 1 object file(s)
(pythoc_test) C:\Users\thoma\projects\pythoc_test>build\test5.exe
Adding 10 and 20 = 30
Mais est-ce que cela en vaut vraiment la peine ?
Toutes les choses dont nous avons parlé n’en vaudront la peine que si nous constatons de réelles améliorations de vitesse dans notre code. Donc, pour notre dernier exemple, voyons à quelle vitesse nos programmes compilés peuvent être comparés à leur équivalent en Python, et cela devrait répondre définitivement à notre question.
Tout d’abord, le code Python normal. Nous utiliserons un calcul récursif de Fibonacci pour simuler un processus de longue durée. Calculons le quarantième nombre de Fibonacci.
import time
def fib(n):
# This calculates the sequence recursively
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
if __name__ == "__main__":
print("Starting Standard Python speed test...")
start_time = time.time()
# fib(38) usually takes around 10 seconds in Python,
# depending on your computer's CPU.
result = fib(40)
end_time = time.time()
print(f"Result: {result}")
print(f"Time taken: {end_time - start_time:.4f} seconds")
J’ai obtenu ce résultat en exécutant le code ci-dessus.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test6.py
Starting Standard Python speed test...
Result: 102334155
Time taken: 15.1611 seconds
Passons maintenant au code basé sur Python. Encore une fois, comme pour l’instruction print de notre exemple précédent, nous ne pouvons pas simplement utiliser la directive de timing d’importation standard de Python pour nos timings. Au lieu de cela, nous devons emprunter la fonction de synchronisation standard directement au langage de programmation C : horloge(). Nous définissons cela de la même manière que l’instruction printf que nous avons utilisée précédemment.
Voici le script Python mis à jour avec le minuteur C intégré.
from pythoc import compile, i32, ptr, i8, extern
# 1. Import C's printf
@extern
def printf(fmt: ptr[i8], *args) -> i32:
pass
# 2. Import C's clock function
@extern
def clock() -> i32:
pass
@compile
def fib(n: i32) -> i32:
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
@compile
def main() -> i32:
printf("Starting PythoC speed test...\n")
# Get the start time (this counts in "ticks")
start_time = clock()
# Run the heavy calculation
result = fib(40)
# Get the end time
end_time = clock()
# Calculate the difference.
# Note: On Windows, 1 clock tick = 1 millisecond.
elapsed_ms = end_time - start_time
printf("Result: %d\n", result)
printf("Time taken: %d milliseconds\n", elapsed_ms)
return 0
if __name__ == "__main__":
from pythoc import compile_to_executable
compile_to_executable()
Ma sortie cette fois était,
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test7.py
Successfully compiled to executable: build\test7.exe
Linked 1 object file(s)
(pythoc_test) C:\Users\thoma\projects\pythoc_test>build\test7.exe
Starting PythoC speed test...
Result: 102334155
Time taken: 308 milliseconds
Et dans ce petit exemple, bien que le code soit légèrement plus complexe, nous voyons le réel avantage d’utiliser des langages compilés comme le C. Notre exécutable était 40 fois plus rapide que le code Python équivalent. Pas trop mal.
À qui s’adresse PythonC ?
Je vois trois principaux types d’utilisateurs pour Python.
1/ Comme nous l’avons vu dans notre test de vitesse de Fibonacci, Python standard peut être lent lors de tâches mathématiques lourdes. PythonC pourrait être utile à tout développeur Python créant des simulations physiques, des algorithmes complexes ou des pipelines de traitement de données personnalisés qui ont atteint un mur de performances.
2/ Les programmeurs qui travaillent en étroite collaboration avec du matériel informatique (comme la création de moteurs de jeux, l’écriture de pilotes ou la programmation de petits appareils IoT) écrivent généralement en C car ils doivent gérer manuellement la mémoire de l’ordinateur.
PythoC pourrait plaire à ces développeurs car il offre le même contrôle manuel de la mémoire (en utilisant des pointeurs et des types natifs), mais il leur permet d’utiliser Python comme moteur de « métaprogrammation » pour écrire du code plus propre et plus flexible avant qu’il ne soit compilé au niveau matériel.
3/ Si vous écrivez un script Python utile et souhaitez le partager avec un collègue, ce collègue doit généralement installer Python, configurer un environnement virtuel et télécharger vos dépendances. Cela peut s’avérer fastidieux, surtout si l’utilisateur cible n’est pas très compétent en informatique. Avec PythoC, cependant, une fois que vous avez votre exécutable C compilé, n’importe qui peut l’exécuter simplement en double-cliquant sur le fichier.
Et pour qui ce n’est pas
Le revers de la médaille est que PythoC n’est probablement pas le meilleur outil pour un développeur Web, car les goulots d’étranglement en termes de performances sont généralement dus à la vitesse du réseau ou de la base de données, et non à la vitesse de calcul du processeur.
De même, si vous êtes déjà un utilisateur de bibliothèques optimisées telles que NumPy, vous ne verrez pas non plus beaucoup d’avantages.
Résumé
Cet article vous a présenté la bibliothèque PythonC relativement nouvelle et inconnue. Avec lui, vous pouvez utiliser Python pour créer du code exécutable C autonome ultra-rapide.
J’ai donné plusieurs exemples d’utilisation de Python et de la bibliothèque PythoC pour produire des programmes exécutables en C, dont un qui a montré une accélération incroyable lors de l’exécution de l’exécutable produit par la bibliothèque PythoC par rapport à un programme Python standard.
Un problème que vous rencontrerez est que les importations Python ne sont pas prises en charge dans les programmes PythoC, mais j’ai également montré comment contourner ce problème en les remplaçant par des éléments intégrés C équivalents.
Enfin, j’ai discuté des types de programmeurs Python qui, selon moi, pourraient voir un avantage à utiliser PythonC dans leurs charges de travail, et de ceux qui ne le verraient pas.
J’espère que cela vous a aiguisé l’appétit de voir pour quels types de cas d’utilisation vous pouvez exploiter PythoC. Vous pouvez en apprendre beaucoup plus sur cette bibliothèque utile en consultant le dépôt GitHub au lien suivant.



