Déboguer Python et C++ ensemble dans Visual Studio

La plupart des débogueurs Python réguliers prennent en charge le débogage du code Python uniquement, mais il est courant pour les développeurs d’utiliser Python avec C ou C++. Certains scénarios qui utilisent du code mixte sont des applications qui nécessitent une haute performance ou la capacité d’invoquer directement les API de plate-forme sont souvent codées en Python et en C ou C++.

Visual Studio fournit un débogage mixte intégré et simultané pour le code Python et le code natif C/C++. Le support est disponible lorsque vous sélectionnez l’option Outils de développement natif Python pour la charge de travail Développement Python dans l’installateur de Visual Studio :

Capture d’écran montrant l'option des outils de développement natifs Python sélectionnée dans le programme Visual Studio Installer.

Dans cet article, vous explorez comment travailler avec les fonctionnalités de débogage mixte suivantes :

  • Piles d’appels combinées
  • Pas à pas détaillé alternant entre du code Python et natif
  • Points d’arrêt dans les deux types de code
  • Visualiser les représentations Python des objets dans les cadres natifs et vice versa
  • Débogage dans le contexte du projet Python ou du projet C++

Capture d’écran montrant un exemple de débogage en mode mixte pour le code Python et C++ dans Visual Studio.

Prérequis

  • Visual Studio 2017 et ses versions ultérieures. Le débogage mixte n’est pas disponible avec les outils Python pour Visual Studio 1.x dans Visual Studio 2015 et les versions antérieures.

  • Visual Studio installé avec le support pour les charges de travail Python. Pour plus d’informations, veuillez consulter la rubrique Installer le support Python dans Visual Studio.

Activer le débogage en mode mixte dans un projet Python

Les étapes suivantes décrivent comment activer le débogage mixte dans un projet Python :

  1. Dans l’Explorateur de solutions, faites un clic droit sur le projet Python, et sélectionnez Propriétés.

  2. Dans le volet Propriétés, sélectionnez l’onglet Débogage, puis sélectionnez l’option Débogage>Activer le débogage du code natif :

    Capture d’écran montrant comment définir la propriété Activer le débogage du code natif dans Visual Studio.

    Cette option active le mode mixte pour toutes les sessions de débogage.

    Conseil

    Lorsque vous activez le débogage du code natif, la fenêtre de sortie Python peut se fermer immédiatement après la fin du programme sans mettre en pause et afficher l’invite Appuyez sur une touche pour continuer. Pour forcer la pause et l’invite après avoir activé le débogage du code natif, ajoutez l’argument -i au champ Exécuter>Arguments de l’interpréteur de l’onglet Débogage. Cet argument place l’interprète Python en mode interactif après l’exécution du code. Le programme attend que vous sélectionniez Ctrl+Z+Entrée pour fermer la fenêtre.

  3. Sélectionnez Fichier>Enregistrer (ou Ctrl+S) pour enregistrer les modifications de propriété.

  4. Pour attacher le débogueur mixte à un processus existant, sélectionnez Déboguer>Attacher au processus. Une boîte de dialogue s’ouvre.

    1. Dans la boîte de dialogue Attacher au processus, sélectionnez le processus approprié dans la liste.

    2. Pour le champ Attacher à, utilisez l’option Sélectionner pour ouvrir la boîte de dialogue Sélectionner le type de code.

    3. Dans la boîte de dialogue Sélectionner le type de code, choisissez l’option Déboguer ces types de code.

    4. Dans la liste, sélectionnez la case à cocher Python (natif) et sélectionnez OK :

      Capture d’écran montrant comment sélectionner le type de code Python (natif) pour le débogage dans Visual Studio.

    5. Sélectionnez Attacher pour démarrer le débogueur.

    Les paramètres de type de code sont persistants. Si vous souhaitez désactiver le débogage mixte et vous attacher à un processus différent plus tard, décochez la case à cocher Python (natif) et sélectionnez la case à cocher Natif.

    Vous pouvez sélectionner d’autres types de code en plus de ou au lieu de l’option Natif. Par exemple, si une application gérée héberge CPython, qui à son tour utilise des modules d’extension natifs, et que vous souhaitez déboguer les trois projets de code, sélectionnez les cases à cocher Python, Natif, et Géré. Cette approche vous offre une expérience de débogage unifiée incluant des piles d’appels combinées et des passages entre les trois environnements d’exécution.

Travailler avec des environnements virtuels

Lorsque vous utilisez cette méthode de débogage mixte pour des environnements virtuels (venvs), Python pour Windows utilise un fichier exécutable python.exe pour les venvs que Visual Studio trouve et charge comme un sous-processus.

  • Pour Python 3.8 et versions ultérieures, le mode mixte ne prend pas en charge le débogage multi-processus. Lorsque vous commencez la session de débogage, le sous-processus stub est débogué plutôt que l’application. Pour les scénarios d’attachement, la solution consiste à s’attacher au fichier python.exe correct. Lorsque vous lancez l’application avec débogage (par exemple, via le raccourci clavier F5), vous pouvez créer votre environnement virtuel en utilisant la commande C:\Python310-64\python.exe -m venv venv --symlinks. Dans la commande, insérez votre version favorite de Python. Par défaut, seuls les administrateurs peuvent créer des liens symboliques sur Windows.

  • Pour les versions de Python antérieures à 3.8, le débogage en mode mixte devrait fonctionner comme prévu avec les environnements virtuels.

L’exécution dans un environnement global ne cause pas ces problèmes pour aucune version de Python.

Installer les symboles Python

Lorsque vous démarrez le débogage en mode mixte pour la première fois, vous pourriez voir une boîte de dialogue Symboles Python Requis. Vous devez installer les symboles une seule fois pour chaque environnement Python donné. Les symboles sont automatiquement inclus si vous installez le support Python via l’installateur Visual Studio (Visual Studio 2017 et les versions ultérieures). Pour plus d’informations, veuillez consulter la rubrique Installer les symboles de débogage pour les interpréteurs Python dans Visual Studio.

Accéder au code source Python

Vous pouvez rendre le code source de Python standard disponible lors du débogage.

  1. Accédez à https://www.python.org/downloads/source/.

  2. Téléchargez l’archive du code source de Python appropriée pour votre version, et extrayez le code dans un dossier.

  3. Lorsque Visual Studio demande l’emplacement du code source Python, pointez vers les fichiers spécifiques dans le dossier d’extraction.

Activer le débogage en mode mixte dans un projet C/C++

Visual Studio 2017 version 15.5 et les versions ultérieures prend en charge le débogage en mode mixte à partir d’un projet C/C++. Cette utilisation est par exemple utile lorsque vous souhaitez intégrer Python dans une autre application comme décrit sur python.org.

La procédure suivante explique comment activer le débogage en mode mixte pour un projet C/C++ :

  1. Dans Explorateur de solutions, faites un clic droit sur le projet C/C++, et sélectionnez Propriétés.

  2. Dans le volet Pages des propriétés, sélectionnez l’onglet Propriétés de configuration>Débogage.

  3. Développez le menu déroulant pour l’option Lanceur du débogueur et sélectionnez Débogage Python/Natif.

    Capture d’écran montrant comment sélectionner l'option de débogage natif Python pour un projet C/C++ dans Visual Studio.

    Remarque

    Si vous ne voyez pas l’option Débogage Python/Natif, vous devez d’abord installer les Outils de développement natifs Python en utilisant l’installateur Visual Studio. L’option de débogage native est disponible sous la charge de travail du développement Python. Pour plus d’informations, veuillez consulter la rubrique Installer le support Python dans Visual Studio.

  4. Sélectionnez OK pour enregistrer les modifications.

Déboguer le lanceur de programme

Lorsque vous utilisez cette méthode, vous ne pouvez pas déboguer le lanceur de programme py.exe car il génère un sous-processus enfant python.exe. Le débogueur ne se connecte pas au processus enfant. Pour ce scénario, la solution de contournement consiste à lancer directement le programme python.exe avec des arguments, comme suit :

  1. Sur le volet Pages des propriétés pour le projet C/C++, rendez-vous sur l’onglet Propriétés de configuration>Débogage.

  2. Pour l’option Commande, spécifiez le chemin complet vers le fichier programme python.exe.

  3. Spécifiez vos arguments souhaités dans le champ Arguments de la commande.

Attachez le débogueur en mode mixte

Pour Visual Studio 2017 version 15.4 et les versions antérieures, le débogage en mode mixte direct est activé uniquement lors du lancement d’un projet Python dans Visual Studio. Le support est limité car les projets C/C++ utilisent uniquement le débogueur natif.

Pour ce scénario, la solution de contournement consiste à attacher séparément le débogueur :

  1. Démarrez le projet C++ sans débogage en sélectionnant Déboguer>Démarrer sans débogage ou utilisez le raccourci clavier Ctrl+F5.

  2. Pour attacher le débogueur mixte à un processus existant, sélectionnez Déboguer>Attacher au processus. Une boîte de dialogue s’ouvre.

    1. Dans la boîte de dialogue Attacher au processus, sélectionnez le processus approprié dans la liste.

    2. Pour le champ Attacher à, utilisez l’option Sélectionner pour ouvrir la boîte de dialogue Sélectionner le type de code.

    3. Dans la boîte de dialogue Sélectionner le type de code, choisissez l’option Déboguer ces types de code.

    4. Dans la liste, sélectionnez la case à cocher Python, et sélectionnez OK.

    5. Sélectionnez Attacher pour démarrer le débogueur.

Conseil

Vous pouvez ajouter une pause ou un délai dans l’application C++ pour vous assurer qu’elle n’appelle pas le code Python que vous souhaitez déboguer avant de joindre le débogueur.

Explorer les fonctionnalités spécifiques au mode mixte

Visual Studio propose plusieurs fonctionnalités de débogage en mode mixte pour faciliter le débogage de votre application :

Utilisez une pile d’appels combinée

La fenêtre Pile des appels affiche des frames de pile natifs et Python entrelacés, en insérant des transitions entre les deux :

Capture d’écran de la fenêtre de pile des appels combinée avec débogage en mode mixte dans Visual Studio.

  • Pour que les transitions apparaissent comme [Code externe] sans spécifier la direction de la transition, activez l’option Outils>Options>Débogage>Général>Activer Mon Code Uniquement.

  • Pour rendre n’importe quel cadre d’appel actif, double-cliquez sur le cadre. Cette action ouvre également le code source correspondant, si possible. Si le code source n’est pas disponible, le cadre est quand même rendu actif et les variables locales peuvent être inspectées.

Pas à pas détaillé alternant entre du code Python et natif

Visual Studio propose les commandes Effectuer un pas à pas détaillé (F11) ou Pas à pas sortant (Shift+F11) pour permettre au débogueur en mode mixte de gérer correctement les changements entre les types de code.

  • Lorsque Python appelle une méthode d’un type implémenté en C, le passage à cette méthode s’arrête au début de la fonction native qui implémente la méthode.

  • Ce même comportement se produit lorsque le code natif appelle une fonction API Python qui entraîne l’exécution de code Python. Passer en mode pas à pas dans un appel à PyObject_CallObject sur une valeur de fonction initialement définie en Python s’arrête au début de la fonction Python.

  • L’exécution d’un pas à pas détaillé alternant entre un code Python et natif est également prise en charge pour les fonctions natives appelées à partir de Python par le biais de ctypes.

Utilisez la vue des valeurs PyObject dans le code natif

Quand un frame natif (C ou C++) est actif, ses variables locales s’affichent dans la fenêtre Variables locales du débogueur. Dans les modules d’extension Python natifs, de nombreuses de ces variables sont de type PyObject (qui est un typedef pour _object), ou quelques autres types fondamentaux de Python. En débogage en mode mixte, ces valeurs présentent un autre nœud enfant étiqueté [Vue Python].

  • Pour voir la représentation Python de la variable, développez le nœud. La vue des variables est identique à ce que vous voyez si une variable locale référençant le même objet est présente dans un cadre Python. Les enfants de ce nœud sont modifiables.

    Capture d’écran montrant la vue Python dans la fenêtre Locals de Visual Studio.

  • Pour désactiver cette fonctionnalité, cliquez avec le bouton droit sur un emplacement quelconque de la fenêtre Variables locales, puis désélectionnez l’option de menu Python>Afficher les nœuds de la vue Python :

    Capture d’écran montrant comment activer l’option Afficher les nœuds de vue Python pour la fenêtre Locals.

Types C affichant des nœuds de vue Python

Les types C suivants affichent des nœuds [Vue Python], s’ils sont activés :

  • PyObject
  • PyVarObject
  • PyTypeObject
  • PyByteArrayObject
  • PyBytesObject
  • PyTupleObject
  • PyListObject
  • PyDictObject
  • PySetObject
  • PyIntObject
  • PyLongObject
  • PyFloatObject
  • PyStringObject
  • PyUnicodeObject

[Vue Python] n’apparaît pas automatiquement pour les types que vous avez vous-même créés. Lorsque vous créez des extensions pour Python 3.x, ce manque n’est généralement pas un problème. Tout objet a finalement un champ ob_base de l’un des types C répertoriés, ce qui provoque l’apparition de [Vue Python].

Afficher les valeurs natives dans le code Python

Vous pouvez activer une [Vue C++] pour les valeurs natives dans la fenêtre Variables locales lorsque qu’un cadre Python est actif. Cette fonctionnalité n’est pas activée par défaut.

  • Pour activer la fonctionnalité, faites un clic droit dans la fenêtre Variables locales et activez l’option de menu Python>Afficher les nœuds de vue C++.

    Capture d’écran montrant comment activer les options Afficher les nœuds de vue C++ pour la fenêtre Locals.

  • Le nœud [Vue C++] fournit une représentation de la structure C/C++ sous-jacente pour une valeur, identique à ce que vous voyez dans un cadre natif. Il montre une instance de _longobject (pour laquelle PyLongObject est un typedef) pour un entier long Python, et il essaie d’inférer les types pour les classes natives que vous avez vous-même créées. Les enfants de ce nœud sont modifiables.

    Capture d’écran montrant la vue C++ dans la fenêtre Locals de Visual Studio.

Si un champ enfant d’un objet est de type PyObject, ou un autre type pris en charge, alors il a un nœud de représentation [Vue Python] (si ces représentations sont activées). Ce comportement permet de naviguer dans les graphiques d’objets où les liens ne sont pas directement exposés à Python.

Contrairement aux nœuds [Vue Python], qui utilisent les métadonnées d’objet Python pour déterminer le type de l’objet, il n’existe aucun mécanisme suffisamment fiable pour [C++ view] (Vue C++). En règle générale, pour une valeur Python donnée (autrement dit, une référence PyObject) il est impossible de déterminer avec certitude la structure C/C++ sous-jacente. Le débogueur en mode mixte essaie de deviner le type en regardant divers champs du type de l’objet (comme le PyTypeObject référencé par son champ ob_type) qui ont des types de pointeur de fonction. Si l’un de ces pointeurs de fonction référence une fonction qui peut être résolue, et que cette fonction a un paramètre self avec un type plus spécifique que PyObject*, alors ce type est supposé être le type de support.

Considérez l’exemple suivant, où la ob_type->tp_init valeur pour un objet donné pointe vers la fonction suivante :

static int FobObject_init(FobObject* self, PyObject* args, PyObject* kwds) {
    return 0;
}

Dans ce cas, le débogueur peut déduire correctement que le type C de l’objet est FobObject. Si le débogueur ne peut pas déterminer un type plus précis à partir de tp_init, il passe à d’autres champs. S’il lui est impossible de déduire le type d’après l’ensemble de ces champs, le nœud [C++ view] (Vue C++) présente l’objet sous la forme d’une instance PyObject.

Pour obtenir systématiquement une représentation utile pour les types créés personnalisés, il est préférable d’inscrire au moins une fonction spéciale lors de l’inscription du type, et d’utiliser un paramètre self fortement typé. La plupart des types remplissent cette exigence naturellement. Pour d’autres types, l’inspection tp_init est généralement l’entrée la plus pratique à utiliser à cette fin. Une implémentation factice de tp_init pour un type qui est présent uniquement pour permettre l’inférence de type du débogueur peut simplement renvoyer zéro immédiatement, comme dans l’exemple précédent.

Examinez les différences par rapport au débogage Python standard

Le débogueur en mode mixte est distinct du débogueur Python standard. Il introduit quelques fonctionnalités supplémentaires mais manque de certaines capacités liées à Python, comme suit :

  • Les fonctionnalités non prises en charge incluent les points d’arrêt conditionnels, la fenêtre Interactive Debug, et le débogage à distance multi-plateforme.
  • La fenêtre Exécution est disponible mais avec un sous-ensemble limité de ses fonctionnalités, dont toutes les limitations répertoriées dans cette section.
  • Les versions de Python prises en charge incluent uniquement CPython 2.7 et 3.3+.
  • Pour utiliser Python avec le Shell Visual Studio (par exemple, si vous l’installez avec l’installateur intégré), Visual Studio est incapable d’ouvrir des projets C++. En conséquence, l’expérience d’édition pour les fichiers C++ est celle d’un éditeur de texte de base uniquement. Toutefois, le débogage C/C++ et le débogage en mode mixte sont entièrement pris en charge dans Shell avec le code source, l’exécution d’un pas à pas détaillé dans le code natif et l’évaluation des expressions C++ dans les fenêtres de débogage.
  • Lorsque vous visualisez des objets Python dans les fenêtres d’outils de débogage Variables locales et Espion, le débogueur en mode mixte montre uniquement la structure des objets. Il n’évalue pas automatiquement les propriétés ou ne montre pas les attributs calculés. Dans le cas des collections, il présente uniquement les éléments pour les types de collections intégrés (tuple, list, dict, set). Les types de collection personnalisés ne sont pas visualisés en tant que collections, sauf s’ils sont hérités d’un type de collection intégré.
  • L’évaluation des expressions est gérée comme décrit dans la section suivante.

Utilisez l’évaluation des expressions

Le débogueur Python standard permet l’évaluation d’expressions Python arbitraires dans les fenêtres Espion et Exécution lorsque le processus en cours de débogage est en pause à n’importe quel moment dans le code, tant qu’il n’est pas bloqué dans une opération d’E/S ou un autre appel système similaire. Dans le cadre du débogage en mode mixte, les expressions arbitraires ne peuvent être évaluées qu’en cas d’arrêt dans le code Python, après un point d’arrêt ou lors d’un pas à pas détaillé dans le code. Les expressions ne peuvent être évaluées que sur le thread sur lequel le point d’arrêt ou l’opération pas à pas se sont produits.

Lorsque le débogueur s’arrête dans le code natif, ou dans le code Python où les conditions décrites ne s’appliquent pas, comme après une opération de retour à l’appelant, ou sur un thread différent). L’évaluation des expressions est limitée à l’accès aux variables locales et globales dans la portée du cadre actuellement sélectionné, à l’accès à leurs champs, et à l’indexation des types de collection intégrés avec des littéraux. Par exemple, l’expression suivante peut être évaluée dans n’importe quel contexte (à condition que tous les identifiants se réfèrent à des variables et champs existants de types appropriés) :

foo.bar[0].baz['key']

Le débogueur en mode mixte résout également de telles expressions d’une autre manière. Toutes les opérations d’accès aux membres ne recherchent que les champs qui font directement partie de l’objet (comme une entrée dans son __dict__ ou __slots__, ou un champ d’une structure native exposée à Python via tp_members), et ignorent toute logique de __getattr__, __getattribute__, ou de descripteur. De même, toutes les opérations d’indexation ignorent __getitem__ et accèdent directement aux structures de données internes des collections.

Pour des raisons de cohérence, ce schéma de résolution des noms est utilisé pour toutes les expressions qui correspondent aux contraintes pour l’évaluation limitée des expressions. Ce schéma est appliqué indépendamment de savoir si des expressions arbitraires sont autorisées au point d’arrêt actuel. Pour imposer la sémantique Python appropriée lorsqu’un évaluateur complet est disponible, indiquez l’expression entre parenthèses :

(foo.bar[0].baz['key'])