Ajoutez un peu de vitesse native et de sécurité mémoire à votre code Python en créant vos propres packages dans Rust
Rust, parfois appelé Rustlang, est un langage préféré et apprécié de nombreux programmeurs… malgré le fait qu’il a été créé chez Mozilla il y a seulement un peu plus d’une décennie ! Certains d’entre vous qui lisez ceci ont peut-être rencontré occasionnellement un article ou une vidéo YouTube faisant la promotion de ce nouveau langage incroyablement performant, mais qu’est-ce que c’est exactement ?
Rust est un langage de programmation à typage statique conçu pour les performances et la sécurité, en particulier la gestion simultanée et la mémoire sécurisées. Sa syntaxe est similaire à celle de C++… Étant donné que Rust n’a pas de ramasse-miettes fonctionnant en permanence, ses projets peuvent être utilisés comme bibliothèques par d’autres langages de programmation via des interfaces de fonctions étrangères. Il s’agit d’un scénario idéal pour les projets existants où il est essentiel d’assurer des performances élevées tout en maintenant la sécurité de la mémoire
Encore plus impressionnant, selon un sondage Stack Overflow, Rust a maintenu être le « Les plus aimés» langue par la communauté pendant six ans !
Python, d’autre part, a réussi à rester constamment dans le top trois des langages les plus recherchés au cours des dernières années. Bien qu’il y ait ceux qui croient que Python est un peu surestimé et ne mérite peut-être pas d’être dans les meilleurs classements, je pense (ainsi que d’innombrables autres) qu’il mérite très certainement une telle position !
Le graphique ci-dessous d’un autre sondage Stack Overflow n’est que l’un des nombreux qui montrent une tendance similaire décrivant la popularité constante de Python :
Nous avons donc établi que Python est un langage extrêmement populaire et (toujours) en croissance, mais est-ce tout ce qu’il y a dans l’histoire ? Non, pas vraiment… même si la version la plus récente de Python 3.11 apporte des gains de vitesse significatifs au langage (et ils sont significatifs !), il y a toujours l’énorme éléphant dans la pièce que de nombreux programmeurs chevronnés souligneront lorsqu’ils expliqueront pourquoi ils ne le font pas. t tout écrire en Python uniquement : c’est un langage interprété qui a tendance à manquer de capacité de performance à haut volume et à bas niveau.
Cependant, étant une langue interprétée, ne le rend pas mauvais par tous les moyens! Il y a une raison (enfin, plusieurs d’entre elles) pour laquelle Python a continué à maintenir sa domination en tant que langage vraiment apprécié et facile à utiliser.
Le problème cependant, c’est que parce que c’est un langage interprété, il doit passer par un just in time
compilateur qui se trouve sur le système cible au moment de l’exécution. Cela le rend légèrement moins efficace que d’autres langages tels que Rust, Go et C/C++ , qui se compilent tous en binaires de code machine natifs que les systèmes peuvent exécuter immédiatement. sans pour autant la nécessité d’un programme intermédiaire.
Je suis sûr que beaucoup d’entre vous, y compris moi-même, ont voulu utiliser l’excellente lisibilité, la flexibilité et le volume de package qu’offre Python… mais également obtenir les améliorations de performances supérieures offertes par Rust lorsqu’ils doivent effectuer des tâches intensives de niveau inférieur. Il n’y a rien de mal à vouloir avoir son gâteau et le manger aussi ! Après tout, à quoi bon manger du gâteau si vous ne pouvez pas le manger ?!
En l’occurrence, il existe une excellente option disponible qui nous permet de réaliser ces deux choses !
Pour le reste de cet article, nous examinerons un excellent package appelé pyo3
qui nous permettra d’écrire et de publier nos propres packages Python en utilisant Rust. Ensuite, nous pouvons les installer et les importer dans notre projet Python en utilisant pip
comme nous le ferions pour n’importe quel autre module…très facile!
Le besoin réel d’un projet comme celui-ci pourrait provenir d’un certain nombre de scénarios différents :
- Vous avez une équipe plus compétente dans la création d’applications de ligne de commande basées sur Python, mais vous devez incorporer des tâches d’E/S intensives qui nécessitent un langage de bas niveau plus efficace comme Rust pour y parvenir.
- Vous avez déjà développé un projet en utilisant Python, mais souhaitez que les futurs ajouts de code soient écrits en Rust afin de tirer parti de ses performances et de la sécurité des ressources
- Python syntaxe simple et une pléthore de tiers paquets faites-en un excellent choix pour créer une application de terminal… combinez cela avec du code Rust natif pour les tâches de bas niveau et vous avez un sacré framework à votre disposition !
Quelle que soit la raison pour laquelle vous avez besoin d’une structure de projet hybride comme celle-ci, l’objectif reste le même :
Réunissez le meilleur des deux langages pour créer des programmes extrêmement performants, sûrs en mémoire et d’apparence fantastique.
Après avoir assemblé les éléments de ce petit projet, vous devriez avoir une bonne compréhension des possibilités qui s’offrent à vous si jamais vous vouliez ou ayez besoin d’utiliser Rust pour améliorer une application basée sur Python !
L’ensemble du projet que nous allons créer ici sera composé de deux sous-sections :
- Un crate Rust composé de fonctions de test que notre application Python va importer et invoquer (Partie 1)
- Une application Python qui servira de base principale à notre interface de ligne de commande (Partie 2)
Cela peut sembler beaucoup de travail en surface, mais en réalité, tout est relativement simple et simple à développer et à développer.
Si vous souhaitez télécharger l’exemple de code pour cette partie du projet, n’hésitez pas à visiter et à cloner le dépôt correspondant sur GitHub !
Tout d’abord, si vous n’avez pas installé Rust et son gestionnaire de paquets Cargo, vous voudrez peut-être aller de l’avant et le faire très rapidement.
- Sous Unix, exécutez
curl -sSf | sh
dans ta coquille. Cela télécharge et exécuterustup-init.sh
qui à son tour télécharge et exécute la version correcte durustup-init
exécutable pour votre plateforme. - Sous Windows, téléchargez et exécutez
rustup-init.exe
Pour construire les fondations de notre nouvelle caisse, nous allons utiliser deux outils importants, pyo3
et maturin
…ce dernier nous permettra de générer facilement le squelette nécessaire pour exporter notre code Rust à utiliser avec Python. Il est extrêmement simple d’installer ces dernières exigences et, en fonction de votre système d’exploitation, vous pouvez y parvenir en procédant comme suit :
- Les utilisateurs de macOS peuvent installer à l’aide de Homebrew en exécutant
brew install maturin
- Tous peuvent choisir de l’installer globalement en utilisant
pip install maturin
- L’approche recommandée consiste à installer
maturin
dans un environnement virtuel isolé en utilisantpipx
Avec cela à l’écart, allez-y et accédez à l’emplacement où vous souhaitez créer le projet, puis à l’aide de votre shell préféré, exécutez ce qui suit :
maturin new -b pyo3 rusty-python && cd rusty-python
Cela générera notre structure de projet de base, y compris les éléments nécessaires Cargo.toml
fichier, auquel nous ajouterons nos dépendances nécessaires, ainsi qu’un pyproject.toml
file — que Python utilisera pour construire et installer notre paquet le moment venu. Le -b pyo3
le drapeau raconte maturin
que nous utiliserons ce framework pour créer notre nouveau module.
Vous remarquerez que maturin
a également créé un src/lib.rs
déposer pour nous. C’est là que résideront tout le code et les fonctionnalités de notre caisse.
Il contient déjà un passe-partout simple pour montrer comment nous pouvons créer des exportations de fonctions pour notre application Python à utiliser :
use pyo3::prelude::*;/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust.
#[pymodule]
fn rusty_python(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
Notre caisse est composée de différentes définitions de fonctions qui sont annotées avec le pyfunction
attribut. Pour utiliser ces fonctions, nous définissons un autre corps de fonction annoté avec le pymodule
attribut, puis implémentez l’attribut pyfunction(s)
en utilisant add_function
:
/// Says hello.
#[pyfunction]
fn say_hello(name: &str) {
println!("Hello there {name}!");
}/// Runs several test loops
#[pyfunction]
fn run_loops() {
logger::info("Running test loops...");
let mut _count: u32 = 0;
for _ in 0..1000 {
for _ in 0..100 {
_count += 1;
}
}
print!("\n");
logger::debug("Process Finished");
}
#[pymodule]
fn rusty_python(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
m.add_function(wrap_pyfunction!(say_hello, m)?)?;
m.add_function(wrap_pyfunction!(run_loops, m)?)?;
Ok(())
}
Le passe-partout à lui seul nous fournit une solution de travail à tester avec Python, mais à des fins de démonstration, nous allons continuer et créer un autre pyfunction
qui imprime un message Hello en utilisant une entrée fournie. Vous pouvez voir dans le Informations sur le code ci-dessus juste comment s’y prendre:
- Déclarez deux nouvelles fonctions en utilisant le
pyfunction
attribut - Ajouter le nouveau créé
pyfunctions
à notrerusty-python
module
Afin d’installer facilement notre nouvelle caisse Rust et de l’importer dans de futurs projets Python, il est impératif que nous la téléchargions sur un système de contrôle de version comme Github
ou alors Gitlab
(ou tout autre service que vous pourriez utiliser).
Pour ceux qui ne sont pas familiers avec le processus de création d’un référentiel et d’envoi de notre code, voici un bref aperçu :
- Connectez-vous à votre compte GitHub/Gitlab et créez un nouveau dépôt vide
- Revenez à votre terminal et à partir de la racine de votre répertoire de projet, exécutez
git init
- Une fois que vous avez initialisé les éléments, vous devez le lier à votre référentiel distant afin de pouvoir y appliquer toutes les nouvelles modifications. Exécutez simplement ce qui suit :
git remote add origin https://github.com/<username>/<repo-name>.git
git branch -M main
git add .
git commit -m "First Commit"
git push -u origin main
En guise de récapitulatif rapide, voici ce que nous devions mettre en place pour notre application hybride Rust-Python :
Si vous souhaitez continuer à suivre le code de cette section du projet, rendez-vous sur le référentiel GitHub et clonez-le !
Il est maintenant temps de terminer la dernière étape de notre projet ! Si vous ne l’avez pas encore remarqué, nous allons avoir besoin d’installer Python pour finir les choses !
- Pour les utilisateurs de Linux, ouvrez simplement votre terminal et exécutez
sudo pacman install python3
,sudo apt install -y python3
, etc. selon votre gestionnaire de paquets. Python devrait déjà être installé sur votre système, mais je recommande vivement d’avoir au moinsv3.8+
(encore mieux serait d’installer3.11
ce qui est un peu plus rapide). - Sous Windows et macOS, vous pouvez télécharger les exécutables du programme d’installation depuis python.org
Pour assembler ce petit exemple, nous allons simplement utiliser une poignée de packages pour fournir une base de code de travail qui peut être développée et utilisée dans d’autres projets.
Créez un nouveau dossier de projet pour l’application Python et créez quelques exemples de fichiers à tester :
├── api
│ ├── __init__.py
│ └── update.py
└── cmd.py
Le api
Le répertoire du module contient simplement quelques fonctions que nous pouvons utiliser dans notre application principale en ligne de commande pour simuler des importations d’applications typiques :
import time
from rich.progress import trackdef checkVersion(name):
version = "1.0.0"
getUpdate(f"Fetching current version for {name}...")
print(f"\n[+] All Good! You're using the most recent of {name} --> {version}\n")
def getUpdate(description):
for i in track(range(100), description=description):
time.sleep(.1) # Simulate work being done
Si vous avez remarqué l’instruction d’importation faisant référence à rich
et ne connaissent pas la bibliothèque, Vérifiez-le! L’une des principales raisons pour lesquelles j’ai toujours aimé créer des applications de terminal Python est due à la grande quantité de flexibilité et d’options de bibliothèque qu’il offre aux développeurs qui cherchent à créer quelque chose de génial !
Le principal cmd.py
file est l’endroit où réside la logique de notre application en ligne de commande :
#!/usr/bin/env python
import argparse
from api import update
import rusty_python as rpd = """
API test application using a combination
of pure Python functions and additional
helper modules written in Rust.
This application uses several Python libraries to
create a colorful commandline app with example
functionality implemented in Rust
"""
def cli():
parser = argparse.ArgumentParser(
description=d
)
parser.add_argument(
"-n", "--app-name",
action="store",
required=False,
help="App name to use for update download simulation"
)
parser.add_argument(
"-l", "--loop",
action="store_true",
required=False,
help="Run some test loops!. Uses `run_loops` implemented in Rust"
)
parser.add_argument(
"-r", "--rust-arg",
action="store",
required=False,
help="""
Tell us your name! This `string` value gets passed
to the `say_hello` function implemented in Rust. The function also runs
multiple `async` requests
"""
)
args = parser.parse_args()
if args:
if args.app_name:
update.checkVersion(args.app_name)
if args.loop:
rp.run_loops()
if args.rust_arg:
rp.say_hello(args.rust_arg)
rp.begin_request_test()
else:
parser.print_usage()
if __name__ == '__main__':
cli()
Même si le code ci-dessus est assez simple, c’est exactement le même type de structure que vous utiliserez dans les projets futurs à mesure qu’ils grandissent en complexité et en échelle. La conception suit les mêmes directives standard lors de l’assemblage d’un CLI
application:
- Installez et importez le
argparse
bibliothèque pour gérer la définition et la lecture des arguments d’entrée pendant l’exécution du programme - Définir notre principal
cli
fonction pour gérer toute la logique du programme - Créer et instancier un nouveau
parser
objet, ainsi que définir les différents drapeaux et arguments que l’application acceptera
Le moment est enfin venu de tester le projet et d’utiliser notre package personnalisé implémenté dans Rust à partir de l’article précédent ! Avant de sauter devant et de courir cmd.py
cependant, gardez à l’esprit que quelques-uns des modules importés dans notre application ne sont pas immédiatement disponibles pour Python !
Rappelez-vous, nous avons inclus le rich
bibliothèque pour aider à améliorer l’apparence de notre application et fournir des fonctionnalités supplémentaires à mesure que notre projet s’agrandit. Nous avons également ajouté l’instruction d’importation pour nous permettre d’appeler des fonctions de notre package implémenté dans Rust.
Pour mettre les dernières pièces en place, nous avons juste besoin d’exécuter quelques pip
commandes pour installer les dépendances manquantes :
$ pip install rich
$ pip install -i rusty-python
Une fois les dernières dépendances installées, nous pouvons enfin exécuter notre nouvelle petite application en ligne de commande et tester certaines choses ! Comme avec la plupart des applications CLI, c’est généralement une bonne idée de commencer à exécuter le code sans aucun argument autre que -h
ou alors — help
pour vérifier que nos informations d’utilisation sont imprimées pour nos utilisateurs :
Jusqu’ici tout va bien! Après cela, nous pouvons tester les fonctions Python régulières en passant des arguments à l’application…puis faites de même pour la fonction qui vient de notre paquet Rust. Si tout se passe bien, il ne devrait pas y avoir d’erreurs ou de plantages inattendus… la sortie devrait être comme n’importe quel autre appel de fonction normal.
Cela peut sembler un peu décevant après avoir parcouru les différentes étapes pour en arriver là, mais en réalité, vous avez réussi à intégrer des fonctionnalités implémentées dans Rust et à les utiliser dans une application Python… vos options à partir de maintenant sont pratiquement illimitées !