Maison développement back-end Tutoriel Python Comment j'ai ajouté la prise en charge des fonctions imbriquées dans le bytecode Python

Comment j'ai ajouté la prise en charge des fonctions imbriquées dans le bytecode Python

Dec 31, 2024 pm 06:58 PM

How I added support for nested functions in Python bytecode

Je voulais partager des trucs plutôt sympas J'ai découvert le bytecode Python avec vous, notamment comment j'ai ajouté la prise en charge des fonctions, mais mon gars de l'imprimerie a dit que je devais le limiter à 500 mots.

C'est une semaine de vacances, il haussa les épaules. Qu'attendez-vous de moi ?

Hors extraits de code, j'ai négocié.

Bien, a-t-il cédé.

Savez-vous pourquoi nous utilisons le bytecode en premier lieu ?

Je fais juste fonctionner la presse à imprimer, mais je te fais confiance.

Assez juste. Commençons.

Pourquoi nous utilisons le bytecode en premier lieu

Memphis, mon interpréteur Python écrit en Rust, dispose de deux moteurs d'exécution. Aucun des deux ne peut exécuter tout le code, mais les deux peuvent exécuter du code.

Mon interprète de promenade dans les arbres est ce que vous construireriez si vous ne saviez pas ce que vous faisiez. ?‍♂️ Vous tokenisez le code Python d'entrée, générez un arbre de syntaxe abstraite (AST), puis parcourez l'arbre et évaluez chaque nœud. Les expressions renvoient des valeurs et les instructions modifient la table des symboles, qui est implémentée sous la forme d'une série de portées respectant les règles de portée de Python. Rappelez-vous simplement le LEGB pneumonique facile : local, englobant, global, intégré.

Ma machine virtuelle bytecode est ce que vous créeriez si vous ne saviez pas ce que vous faisiez mais vouliez agir comme vous le faisiez. Aussi ?‍♂️. Pour ce moteur, les jetons et l'AST fonctionnent de la même manière, mais plutôt que de marcher, nous décollons du sprint. Nous compilons l'AST en une représentation intermédiaire (IR) ci-après appelée bytecode. Nous créons ensuite une machine virtuelle (VM) basée sur une pile, qui agit conceptuellement comme un processeur, exécutant les instructions de bytecode en séquence, mais elle est entièrement implémentée dans le logiciel.

(Pour un guide complet des deux approches sans divagations, Crafting Interpreters est excellent.)

Pourquoi faisons-nous cela en premier lieu ? N'oubliez pas les deux P : portabilité et performances. Rappelez-vous qu'au début des années 2000, personne ne se taisait sur la portabilité du bytecode Java ? Tout ce dont vous avez besoin est une JVM et vous pouvez exécuter un programme Java compilé sur n'importe quelle machine ! Python a choisi de ne pas suivre cette approche pour des raisons à la fois techniques et marketing, mais en théorie, les mêmes principes s'appliquent. (En pratique, les étapes de compilation sont différentes et je regrette d'ouvrir cette boîte de Pandore.)

La performance est cependant le plus important. Plutôt que de parcourir un AST plusieurs fois au cours de la durée de vie d'un programme, l'IR compilé est une représentation plus efficace. Nous constatons une amélioration des performances en évitant la surcharge liée au parcours répété d'un AST, et sa structure plate se traduit souvent par une meilleure prédiction de branchement et une meilleure localisation du cache au moment de l'exécution.

(Je ne vous en veux pas de ne pas penser à la mise en cache si vous n'avez pas d'expérience en architecture informatique. Bon sang, j'ai commencé ma carrière dans cette industrie et je pense beaucoup moins à la mise en cache qu'à la façon d'éviter écrire deux fois la même ligne de code. Alors faites-moi confiance sur la performance. C'est mon style de leadership : une confiance aveugle.)

Hé mon pote, ça fait 500 mots. Nous devons charger le cadre et le laisser déchirer.

Déjà ?! Vous avez exclu des extraits de code ?

Il n'y a pas d'extraits de code, mon homme.

D'accord, d'accord. Juste 500 de plus. Je le promets.

Le contexte est important pour les variables Python

J'ai fait un peu de chemin avant de déposer mon implémentation de bytecode VM il y a environ un an : je pouvais définir des fonctions et des classes Python, appeler ces fonctions et instancier ces classes. J'ai réprimé ce comportement avec quelques tests. Mais je savais que ma mise en œuvre était compliquée et que je devrais revoir les fondamentaux avant d’ajouter des éléments plus amusants. C'est maintenant la semaine de Noël et je veux ajouter des trucs amusants.

Considérez cet extrait pour appeler une fonction, en gardant un œil sur le TODO.

fn compile_function_call(
    &mut self,
    name: &str,
    args: &ParsedArguments)
) -> Result<Bytecode, CompileError> {
    let mut opcodes = vec![];

    // We push the args onto the stack in reverse call order so that we will pop
    // them off in call order.
    for arg in args.args.iter().rev() {
        opcodes.extend(self.compile_expr(arg)?);
    }

    let (_, index) = self.get_local_index(name);

    // TODO how does this know if it is a global or local index? this may not be the right
    // approach for calling a function
    opcodes.push(Opcode::Call(index));

    Ok(opcodes)
}
Copier après la connexion
Copier après la connexion

Avez-vous fini de réfléchir ? Nous chargeons les arguments de la fonction sur la pile et « appelons la fonction ». Dans le bytecode, tous les noms sont convertis en index (car l'accès aux index est plus rapide pendant l'exécution de la VM), mais nous n'avons pas vraiment de moyen de savoir si nous avons affaire ici à un index local ou à un index global.

Considérez maintenant la version améliorée.

fn compile_function_call(
    &mut self,
    name: &str,
    args: &ParsedArguments)
) -> Result<Bytecode, CompileError> {
    let mut opcodes = vec![self.compile_load(name)];

    // We push the args onto the stack in reverse call order so that we will pop
    // them off in call order.
    for arg in args.args.iter().rev() {
        opcodes.extend(self.compile_expr(arg)?);
    }

    let argc = opcodes.len() - 1;
    opcodes.push(Opcode::Call(argc));

    Ok(opcodes)
}
Copier après la connexion

Merci d'avoir pris en compte ce code.

Nous prenons désormais en charge les appels de fonctions imbriqués ! Qu'est-ce qui a changé ?

  1. L'opcode Call prend désormais un certain nombre d'arguments de position, plutôt qu'un index de la fonction. Cela indique à la VM combien d'arguments doivent être retirés de la pile avant d'appeler la fonction.
  2. Après avoir retiré les arguments de la pile, la fonction elle-même sera laissée sur la pile et compile_load a déjà géré la portée locale par rapport à la portée globale pour nous.

LOAD_GLOBAL contre LOAD_FAST

Jetons un coup d'œil à ce que fait compile_load.

fn compile_load(&mut self, name: &str) -> Opcode {
    match self.ensure_context() {
        Context::Global => Opcode::LoadGlobal(self.get_or_set_nonlocal_index(name)),
        Context::Local => {
            // Check locals first
            if let Some(index) = self.get_local_index(name) {
                return Opcode::LoadFast(index);
            }

            // If not found locally, fall back to globals
            Opcode::LoadGlobal(self.get_or_set_nonlocal_index(name))
        }
    }
}
Copier après la connexion

Il y a plusieurs principes clés en action ici :

  1. Nous jumelons en fonction du contexte actuel. En adhérant à la sémantique Python, nous pouvons considérer Context::Global comme étant au niveau supérieur de n'importe quel module (pas seulement le point d'entrée de votre script), et Context::Local se trouve à l'intérieur de n'importe quel bloc (c'est-à-dire la définition de fonction ou la définition de classe).
  2. On fait désormais la différence entre un index local et un index non local. (Parce que je devenais fou en essayant de déchiffrer à quoi faisait référence l'index 0 à différents endroits, j'ai introduit les entiers typés. LocalIndex et NonlocalIndex fournissent une sécurité de type pour les entiers non signés autrement non typés. Je pourrais écrire à ce sujet à l'avenir !)
  3. Nous pouvons savoir au moment de la compilation du bytecode si une variable locale existe avec un nom donné, et si ce n'est pas le cas, au moment de l'exécution, nous rechercherons une variable globale. Cela témoigne du dynamisme intégré à Python : tant qu'une variable est présente dans la portée globale de ce module au moment de l'exécution d'une fonction, sa valeur peut être résolue au moment de l'exécution. Cependant, cette résolution dynamique s’accompagne d’une baisse de performances. Alors que les recherches de variables locales sont optimisées pour utiliser les index de pile, les recherches globales nécessitent une recherche dans le dictionnaire d'espace de noms global, ce qui est plus lent. Ce dictionnaire est un mappage de noms avec des objets, qui eux-mêmes peuvent vivre sur le tas. Qui aurait cru que le dicton « Pensez globalement, agissez localement ». faisait-il réellement référence aux scopes Python ?

Qu'y a-t-il dans un nom de variable ?

La dernière chose que je vais vous laisser aujourd'hui est un aperçu de la manière dont ces noms de variables sont mappés. Dans l'extrait de code ci-dessous, vous remarquerez que les index locaux se trouvent dans code.varnames et que les index non locaux se trouvent dans code.names. Les deux vivent sur un CodeObject, qui contient les métadonnées d'un bloc de bytecode Python, y compris ses mappages de variables et de noms.

fn compile_function_call(
    &mut self,
    name: &str,
    args: &ParsedArguments)
) -> Result<Bytecode, CompileError> {
    let mut opcodes = vec![];

    // We push the args onto the stack in reverse call order so that we will pop
    // them off in call order.
    for arg in args.args.iter().rev() {
        opcodes.extend(self.compile_expr(arg)?);
    }

    let (_, index) = self.get_local_index(name);

    // TODO how does this know if it is a global or local index? this may not be the right
    // approach for calling a function
    opcodes.push(Opcode::Call(index));

    Ok(opcodes)
}
Copier après la connexion
Copier après la connexion

La différence entre les noms de variables et les noms m'a tourmenté pendant des semaines (CPython appelle ces noms de variables et noms de variables), mais c'est en fait assez simple. varnames contient les noms de variables pour toutes les variables locales dans une portée donnée, et les noms font de même pour toutes les variables non locales.

Une fois que nous avons correctement suivi cela, tout le reste fonctionne. Au moment de l'exécution, la VM voit un LOAD_GLOBAL ou un LOAD_FAST et sait qu'il faut chercher dans le dictionnaire d'espace de noms global ou dans la pile locale, respectivement.

Mon pote ! M. Gutenberg est au téléphone et dit que nous ne pouvons plus tenir les presses.

D'accord ! Bien! Je comprends! Envoyons-le. ?

Quelle est la prochaine étape pour Memphis ?

Chut ! L’imprimerie ne sait pas que j’écris une conclusion, donc je serai bref.

Avec une portée variable et des appels de fonctions dans un endroit solide, je tourne progressivement mon attention vers des fonctionnalités telles que les traces de pile et la prise en charge asynchrone. Si vous avez apprécié cette plongée dans le bytecode ou si vous avez des questions sur la création de votre propre interprète, j'aimerais avoir de vos nouvelles : laissez un commentaire !


Abonnez-vous et économisez [sur rien]

Si vous souhaitez recevoir plus de messages comme celui-ci directement dans votre boîte de réception, vous pouvez vous abonner ici !

Travailler avec moi

J'encadre des ingénieurs logiciels pour qu'ils relèvent les défis techniques et évoluent dans leur carrière dans un environnement favorable, parfois idiot. Si vous êtes intéressé, vous pouvez réserver une séance ici.

Ailleurs

En plus du mentorat, j'écris également sur mon expérience de travail indépendant et d'autisme diagnostiqué tardivement. Moins de code et le même nombre de blagues.

  • Café effet lac, chapitre 2 - From Scratch dot org

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

<🎜>: Bubble Gum Simulator Infinity - Comment obtenir et utiliser les clés royales
4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
<🎜>: Grow A Garden - Guide de mutation complet
3 Il y a quelques semaines By DDD
Nordhold: Système de fusion, expliqué
4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers of the Witch Tree - Comment déverrouiller le grappin
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Tutoriel Java
1676
14
Tutoriel PHP
1278
29
Tutoriel C#
1257
24
Python vs C: courbes d'apprentissage et facilité d'utilisation Python vs C: courbes d'apprentissage et facilité d'utilisation Apr 19, 2025 am 12:20 AM

Python est plus facile à apprendre et à utiliser, tandis que C est plus puissant mais complexe. 1. La syntaxe Python est concise et adaptée aux débutants. Le typage dynamique et la gestion automatique de la mémoire le rendent facile à utiliser, mais peuvent entraîner des erreurs d'exécution. 2.C fournit des fonctionnalités de contrôle de bas niveau et avancées, adaptées aux applications haute performance, mais a un seuil d'apprentissage élevé et nécessite une gestion manuelle de la mémoire et de la sécurité.

Apprendre Python: 2 heures d'étude quotidienne est-elle suffisante? Apprendre Python: 2 heures d'étude quotidienne est-elle suffisante? Apr 18, 2025 am 12:22 AM

Est-ce suffisant pour apprendre Python pendant deux heures par jour? Cela dépend de vos objectifs et de vos méthodes d'apprentissage. 1) Élaborer un plan d'apprentissage clair, 2) Sélectionnez les ressources et méthodes d'apprentissage appropriées, 3) la pratique et l'examen et la consolidation de la pratique pratique et de l'examen et de la consolidation, et vous pouvez progressivement maîtriser les connaissances de base et les fonctions avancées de Python au cours de cette période.

Python vs. C: Explorer les performances et l'efficacité Python vs. C: Explorer les performances et l'efficacité Apr 18, 2025 am 12:20 AM

Python est meilleur que C dans l'efficacité du développement, mais C est plus élevé dans les performances d'exécution. 1. La syntaxe concise de Python et les bibliothèques riches améliorent l'efficacité du développement. Les caractéristiques de type compilation et le contrôle du matériel de CC améliorent les performances d'exécution. Lorsque vous faites un choix, vous devez peser la vitesse de développement et l'efficacité de l'exécution en fonction des besoins du projet.

Python vs C: Comprendre les principales différences Python vs C: Comprendre les principales différences Apr 21, 2025 am 12:18 AM

Python et C ont chacun leurs propres avantages, et le choix doit être basé sur les exigences du projet. 1) Python convient au développement rapide et au traitement des données en raison de sa syntaxe concise et de son typage dynamique. 2) C convient à des performances élevées et à une programmation système en raison de son typage statique et de sa gestion de la mémoire manuelle.

Quelle partie fait partie de la bibliothèque standard Python: listes ou tableaux? Quelle partie fait partie de la bibliothèque standard Python: listes ou tableaux? Apr 27, 2025 am 12:03 AM

PythonlistSaReparmentofthestandardLibrary, tandis que les coloccules de colocède, tandis que les colocculations pour la base de la Parlementaire, des coloments de forage polyvalent, tandis que la fonctionnalité de la fonctionnalité nettement adressée.

Python: automatisation, script et gestion des tâches Python: automatisation, script et gestion des tâches Apr 16, 2025 am 12:14 AM

Python excelle dans l'automatisation, les scripts et la gestion des tâches. 1) Automatisation: La sauvegarde du fichier est réalisée via des bibliothèques standard telles que le système d'exploitation et la fermeture. 2) Écriture de script: utilisez la bibliothèque PSUTIL pour surveiller les ressources système. 3) Gestion des tâches: utilisez la bibliothèque de planification pour planifier les tâches. La facilité d'utilisation de Python et la prise en charge de la bibliothèque riche en font l'outil préféré dans ces domaines.

Python pour l'informatique scientifique: un look détaillé Python pour l'informatique scientifique: un look détaillé Apr 19, 2025 am 12:15 AM

Les applications de Python en informatique scientifique comprennent l'analyse des données, l'apprentissage automatique, la simulation numérique et la visualisation. 1.Numpy fournit des tableaux multidimensionnels et des fonctions mathématiques efficaces. 2. Scipy étend la fonctionnalité Numpy et fournit des outils d'optimisation et d'algèbre linéaire. 3. Pandas est utilisé pour le traitement et l'analyse des données. 4.Matplotlib est utilisé pour générer divers graphiques et résultats visuels.

Python pour le développement Web: applications clés Python pour le développement Web: applications clés Apr 18, 2025 am 12:20 AM

Les applications clés de Python dans le développement Web incluent l'utilisation des cadres Django et Flask, le développement de l'API, l'analyse et la visualisation des données, l'apprentissage automatique et l'IA et l'optimisation des performances. 1. Framework Django et Flask: Django convient au développement rapide d'applications complexes, et Flask convient aux projets petits ou hautement personnalisés. 2. Développement de l'API: Utilisez Flask ou DjangorestFramework pour construire RestulAPI. 3. Analyse et visualisation des données: utilisez Python pour traiter les données et les afficher via l'interface Web. 4. Apprentissage automatique et AI: Python est utilisé pour créer des applications Web intelligentes. 5. Optimisation des performances: optimisée par la programmation, la mise en cache et le code asynchrones

See all articles