Depuración de Python y C++ juntos en Visual Studio

La mayoría de los depuradores de Python normales solo admiten la depuración de código de Python, pero es habitual que los desarrolladores usen Python con C o C++. Algunos escenarios que usan código mixto son aplicaciones que requieren un alto rendimiento o la capacidad de invocar directamente las API de plataforma y que a menudo se codifican en Python y C o C++.

Visual Studio proporciona depuración integrada y simultánea en modo mixto para código de Python y código de C/C++ nativo. La compatibilidad está disponible al seleccionar la opción Herramientas de desarrollo nativo de Python para la carga de trabajo Desarrollo de Python en el instalador de Visual Studio:

Captura de pantalla donde aparece de la opción de herramientas de desarrollo nativas de Python seleccionadas en el Instalador de Visual Studio.

En este artículo, explorará cómo trabajar con las siguientes características de depuración en modo mixto:

  • Pilas de llamadas combinada
  • Transición entre código de Python y nativo
  • Puntos de interrupción en ambos tipos de código
  • Vista de las representaciones de Python de objetos en marcos nativos y viceversa
  • Depuración en el ámbito de los proyectos de Python o C++

Captura de pantalla donde aparece un ejemplo de depuración en modo mixto para código de Python y C++ en Visual Studio.

Requisitos previos

  • Visual Studio 2017 y versiones posteriores. La depuración en modo mixto no está disponible con Herramientas de Python para Visual Studio 1.x en Visual Studio 2015 y versiones anteriores.

  • Visual Studio instalado con compatibilidad con cargas de trabajo de Python. Para obtener más información, consulte Instalación de la compatibilidad con Python en Visual Studio.

Habilitación de la depuración en modo mixto en un proyecto de Python

En los pasos siguientes se describe cómo habilitar la depuración en modo mixto en un proyecto de Python:

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto de Python y seleccione Propiedades.

  2. En el panel Propiedades, seleccione la pestaña Depurar y, a continuación, seleccione la opción Depurar>Habilitar depuración de código nativo:

    Captura de pantalla donde se indica cómo configurar la propiedad Habilitar depuración de código nativo en Visual Studio.

    Esta opción habilita el modo mixto para todas las sesiones de depuración.

    Sugerencia

    Al habilitar la depuración de código nativo, la ventana de salida de Python podría cerrarse de inmediato al completarse el programa, sin pausarse y mostrando el mensaje Presione cualquier tecla para continuar. Para forzar la pausa y el mensaje después de habilitar la depuración de código nativo, agregue el argumento -i al campo Ejecutar>Argumentos del intérprete en la pestaña Depurar. Este argumento pone al intérprete de Python en modo interactivo después de ejecutar el código. El programa espera a que seleccione Ctrl+Z+Entrar para cerrar la ventana.

  3. Seleccione Archivo>Guardar (o Ctrl+S) para guardar los cambios de propiedad.

  4. Para asociar el depurador en modo mixto a un proceso existente, seleccione Depurar>Asociar al proceso. Se abre un cuadro de diálogo.

    1. En el cuadro de diálogo Asociar al proceso, seleccione el proceso adecuado de la lista.

    2. Para el campo Asociar a, use la opción Seleccionar para abrir el cuadro de diálogo Seleccionar tipo de código.

    3. En el cuadro de diálogo Seleccionar tipo de código, seleccione la opción Depurar estos tipos de código.

    4. En la lista, active la casilla Python (nativo) y seleccione Aceptar:

      Captura de pantalla que indica cómo seleccionar el tipo de código python (nativo) para la depuración en Visual Studio.

    5. Seleccione Asociar para iniciar el depurador.

    La configuración del tipo de código es persistente. Si desea deshabilitar la depuración en modo mixto y adjuntarla a un proceso diferente más adelante, desactive la casilla de verificación de tipo de código Python (nativo) y marque la casilla de verificación de tipo de código Nativo.

    Puede seleccionar otros tipos de código además de Nativo, o en lugar de este. Por ejemplo, si una aplicación administrada hospeda CPython, que a su vez usa módulos de extensión nativos y desea depurar los tres proyectos de código, active las casillas Python, Nativo y Administrado. Este enfoque proporciona una experiencia de depuración unificada, incluidas las pilas de llamadas combinadas y la ejecución paso a paso entre los tres entornos de ejecución.

Trabajo con entornos virtuales

Al usar este método de depuración en modo mixto para entornos virtuales, Python para Windows usa un archivo de código auxiliar python.exe para entornos virtuales que Visual Studio busca y carga como subproceso.

  • Para Python 3.8 y versiones posteriores, el modo mixto no admite la depuración de varios procesos. Al iniciar la sesión de depuración, se depura el subproceso de código auxiliar en lugar de la aplicación. En escenarios de asociación, la solución alternativa consiste en realizar la asociación con el archivo python.exe correcto. Al iniciar la aplicación con depuración (por ejemplo, mediante el método abreviado de teclado F5), puede crear su entorno virtual con el comando C:\Python310-64\python.exe -m venv venv --symlinks. En el comando , inserte la versión preferida de Python. De manera predeterminada, solo los administradores pueden crear vínculos simbólicos en Windows.

  • Para las versiones de Python anteriores a la versión 3.8, la depuración en modo mixto debe funcionar según lo previsto con entornos virtuales.

La ejecución en un entorno global no provocará estos problemas para ninguna versión de Python.

Instalación de símbolos de Python

Al iniciar la depuración en modo mixto por primera vez, es posible que vea un cuadro de diálogo Se necesitan símbolos de Python. Debe instalar los símbolos solo una vez para cualquier entorno de Python. Los símbolos se incluyen automáticamente si instala la compatibilidad con Python mediante el instalador de Visual Studio (Visual Studio 2017 y versiones posteriores). Para obtener más información, consulte Instalación de símbolos de depuración para intérpretes de Python en Visual Studio.

Acceso al código fuente de Python

Puede hacer que el código fuente de Python estándar esté disponible al depurar.

  1. Ir a https://www.python.org/downloads/source/.

  2. Descargue el archivo de código fuente de Python adecuado para su versión y extraiga el código en una carpeta.

  3. Cuando Visual Studio solicita la ubicación del código fuente de Python, apunte a los archivos específicos de la carpeta de extracción.

Habilitación de la depuración en modo mixto en un proyecto de C o C++

Visual Studio 2017, versión 15.5 y posteriores, admite la depuración en modo mixto desde un proyecto de C/C++. Un ejemplo de este uso es cuando quiere insertar Python en otra aplicación, tal como se describe en python.org.

En los pasos siguientes se describe cómo habilitar la depuración en modo mixto para un proyecto de C/C++:

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto de C/C++ y seleccione Propiedades.

  2. En el panel Páginas de propiedades, seleccione la pestaña Propiedades de configuración>Depuración.

  3. Expanda el menú desplegable de la opción Depurador para iniciar y seleccione Depuración nativa/Python.

    Captura de pantalla donde se ve cómo seleccionar la opción de depuración nativa de Python para un proyecto en C/C++ en Visual Studio.

    Nota:

    Si no ve la opción Depuración nativa/Python, debe instalar en primer lugar las Herramientas de desarrollo nativo de Python mediante el instalador de Visual Studio. La opción de depuración nativa está disponible en la carga de trabajo desarrollo de Python. Para obtener más información, consulte Instalación de la compatibilidad con Python en Visual Studio.

  4. Seleccione Aceptar para guardar los cambios.

Depuración del iniciador del programa

Cuando se usa este método, no se puede depurar el iniciador de programa py.exe porque genera un subproceso python.exe secundario. El depurador no se asocia al subproceso. Para este escenario, la solución consiste en iniciar el programa python.exe directamente con argumentos, como se indica a continuación:

  1. En el panel Páginas de propiedades del proyecto de C/C++, vaya a la pestaña Propiedades de configuración>Depuración.

  2. Para la opción Comando, especifique la ruta de acceso completa al archivo de programa python.exe.

  3. Especifique los argumentos que desee en el campo Argumentos de comando.

Asociación del depurador en modo mixto

Para Visual Studio 2017, versión 15.4 y anteriores, la depuración directa en modo mixto solo se habilita al iniciar un proyecto de Python en Visual Studio. La compatibilidad es limitada porque los proyectos de C/C++ solo usan el depurador nativo.

Para este escenario, la solución alternativa consiste en asociar el depurador por separado:

  1. Para iniciar el proyecto de C++ sin depurar, seleccione Depurar>Iniciar sin depurar o use el método abreviado de teclado Ctrl+F5.

  2. Para asociar el depurador en modo mixto a un proceso existente, seleccione Depurar>Asociar al proceso. Se abre un cuadro de diálogo.

    1. En el cuadro de diálogo Asociar al proceso, seleccione el proceso adecuado de la lista.

    2. Para el campo Asociar a, use la opción Seleccionar para abrir el cuadro de diálogo Seleccionar tipo de código.

    3. En el cuadro de diálogo Seleccionar tipo de código, seleccione la opción Depurar estos tipos de código.

    4. En la lista, active la casilla Python y seleccione Aceptar.

    5. Seleccione Asociar para iniciar el depurador.

Sugerencia

Puede añadir una pausa o un retraso en la aplicación de C++ para garantizar que no llama al código de Python que quiere depurar antes de asociar el depurador.

Exploración de las características específicas del modo mixto

Visual Studio proporciona varias características de depuración en modo mixto para facilitar la depuración de la aplicación:

Uso de una pila de llamadas combinada

La ventana Pila de llamadas muestra marcos de pila nativos y de Python intercalados, con marcas de transiciones entre los dos:

Captura de pantalla de la ventana de pila de llamadas combinada con la depuración en modo mixto en Visual Studio.

  • Para que las transacciones aparezcan como [Código externo] sin especificar la dirección de la transición, establezca la opción Herramientas>Opciones>Depuración>General>Habilitar solo mi código.

  • Para activar cualquier marco de llamada, haga doble clic en el marco. Esta acción también abre el código fuente correspondiente, si es posible. Si el código fuente no está disponible, el marco sigue activo y se pueden inspeccionar las variables locales.

Transición entre código de Python y nativo

Visual Studio proporciona los comandos Entrar (F11) o Salir (Mayús+F11) para permitir que el depurador en modo mixto controle correctamente los cambios entre tipos de código.

  • Cuando Python llama a un método de un tipo que está implementado en C, al entrar en una llamada a ese método, se detiene al comienzo de la función nativa que implementa el método.

  • Este mismo comportamiento se produce cuando el código nativo llama a una función de API de Python que hace que se invoque código de Python. Al entrar en una llamada a PyObject_CallObject en un valor de función que se definió originalmente en Python se detiene al comienzo de la función de Python.

  • También es posible pasar de código de Python a nativo para funciones nativas invocadas desde Python mediante ctypes.

Uso de valores PyObject en código nativo

Cuando está activo un marco nativo (C o C++), sus variables locales se muestran en la ventana Variables locales del depurador. En los módulos de extensión nativa de Python, muchas de estas variables son de tipo PyObject (que es una declaración typedef para _object), o algunos otros Tipos de Python fundamentales. En la depuración en modo mixto, estos valores presentan otro nodo secundario con la etiqueta [Vista de Python] .

  • Para ver la representación de Python de la variable, expanda el nodo. La vista de las variables es idéntica a la que ve si una variable local que hace referencia al mismo objeto está presente en un marco de Python. Los elementos secundarios de este nodo son editables.

    Captura de pantalla donde aparece la vista de Python en la ventana Variables locales de Visual Studio.

  • Para deshabilitar esta característica, haga clic con el botón derecho en cualquier parte de la ventana Variables locales y cambie la opción de menú Python>Mostrar nodos de vista de Python:

    Captura de pantalla donde se ve cómo habilitar la opción Mostrar nodos de visualización de Python para la ventana Variables locales.

Tipos de C que muestran nodos de vista de Python

Los tipos de C siguientes muestran nodos de [Vista de Python], si están habilitados:

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

[Vista de Python] no aparece automáticamente para los tipos que crea el usuario. Cuando se crean extensiones para Python 3.x, esta carencia no suele ser un problema. En última instancia, cualquier objeto tiene un campo ob_base de uno de los tipos de C enumerados, lo que hace que aparezca [Vista de Python].

Vista de valores nativos en el código de Python

Puede habilitar una [Vista de C++] para valores nativos en la ventana Locales cuando un marco de Python está activo. Esta característica no está habilitada de forma predeterminada.

  • Para habilitar la característica, haga clic con el botón derecho en la ventana Locales y establezca la opción de menú Python>Mostrar nodos de vista de C++.

    Captura de pantalla donde aparece cómo habilitar las opciones Mostrar nodos de visualización de C++ para la ventana Variables locales.

  • El nodo [Vista de C++] proporciona una representación de la estructura de C/C++ subyacente de un valor, idéntica a la que vería en un marco nativo. Muestra una instancia de _longobject (para la que PyLongObject es una declaración typedef) de un entero largo de Python, e intenta inferir tipos para clases nativas que haya creado usted mismo. Los elementos secundarios de este nodo son editables.

    Captura de pantalla donde se ve la vista de C++ en la ventana Variables locales de Visual Studio.

Si un campo secundario de un objeto es de tipo PyObject o de otro tipo admitido, tiene un nodo de representación de [Vista de Python] (si esas representaciones están habilitadas). Este comportamiento permite navegar por gráficos de objetos en los que los vínculos no se exponen directamente a Python.

A diferencia de los nodos [Vista de Python] , que usan metadatos de objetos de Python para determinar el tipo del objeto, no hay ningún mecanismo similar confiable para [Vista de C++] . Por lo general, dado un valor de Python (es decir, una referencia PyObject), no es posible determinar con seguridad qué estructura de C o C++ lo respalda de manera confiable. El depurador en modo mixto intenta adivinar el tipo examinando los distintos campos del tipo del objeto (como la referencia a PyTypeObject por su campo ob_type) que tienen tipos de puntero de función. Si uno de esos punteros de función hace referencia a una función que se puede resolver, y esa función tiene un parámetro self con un tipo más específico que PyObject*, se asume que ese tipo es el tipo de respaldo.

Considere el ejemplo siguiente, donde el valor ob_type->tp_init de un objeto determinado apunta a la siguiente función:

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

En este caso, el depurador puede deducir correctamente que el tipo de C del objeto es FobObject. Si el depurador no puede determinar un tipo más preciso a partir de tp_init, continúa con los demás campos. Si no puede deducir el tipo a partir de ninguno de esos campos, el nodo [Vista de C++] presenta el objeto como una instancia de PyObject.

Para obtener siempre una representación útil de los tipos personalizados creados, es mejor registrar al menos una función especial cuando se registra el tipo y usar un parámetro self fuertemente tipado. La mayoría de los tipos cumplen ese requisito de forma natural. Para otros tipos, la inspección tp_init suele ser la entrada más conveniente para este propósito. Una implementación ficticia de tp_init para un tipo que está presente exclusivamente para permitir la inferencia del tipo de depurador solo puede devolver cero inmediatamente, como en el ejemplo anterior.

Revisión de las diferencias con la depuración estándar de Python

El depurador en modo mixto es distinto del Depurador de Python estándar. Presenta algunas características adicionales, pero carece de algunas funcionalidades relacionadas con Python, como se indica a continuación:

  • Entre las características no admitidas se incluyen puntos de interrupción condicionales, la ventana Interactiva de depuración y la depuración remota entre plataformas.
  • La ventana Inmediato está disponible, pero con un subconjunto limitado de su funcionalidad, incluidas todas las limitaciones que se indican en esta sección.
  • Entre las versiones de Python admitidas solo se incluyen CPython 2.7 y 3.3+.
  • Para usar Python con Visual Studio Shell (por ejemplo, si lo instala con el instalador integrado), Visual Studio no puede abrir proyectos de C++. Como resultado, la experiencia de edición de los archivos de C++ es solo la de un editor de texto básico. Sin embargo, la depuración de C/C++ y la depuración en modo mixto se admiten completamente en Shell con código fuente, depuración paso a paso por instrucciones del código nativo y evaluación de expresiones de C++ en ventanas del depurador.
  • Al visualizar objetos de Python en las ventanas de herramientas del depurador Locales e Inspección, el depurador en modo mixto muestra solo la estructura de los objetos. No evalúa automáticamente las propiedades ni muestra atributos calculados. Para colecciones, solo muestra elementos para tipos de colección integrados (tuple, list, dict, set). Los tipos de colección personalizados no se visualizan como colecciones, a menos que se hereden de algún tipo de colección integrado.
  • La evaluación de expresiones se controla como se describe en la sección siguiente.

Uso de la evaluación de expresiones

El depurador estándar de Python permite la evaluación de expresiones de Python arbitrarias en las ventanas Inspección e Inmediato cuando el proceso depurado se detiene en cualquier punto del código, siempre que no esté bloqueado en una operación de E/S u otra llamada del sistema similar. En la depuración en modo mixto, las expresiones arbitrarias pueden evaluarse solo cuando se detienen en el código de Python, después de un punto de interrupción o cuando depuran paso a paso por instrucciones el código. Las expresiones solo pueden evaluarse en el subproceso en el que se ha producido el punto de interrupción o la operación de depuración paso a paso por instrucciones.

Cuando el depurador se detiene en código nativo o en código de Python donde no se aplican las condiciones descritas, como después de una operación de salida o en otro subproceso). La evaluación de expresiones se limita a acceder a variables locales y globales en el ámbito del marco seleccionado actualmente, el acceso a sus campos y la indexación de tipos de colección integrados con valores literales. Por ejemplo, la siguiente expresión se puede evaluar en cualquier contexto (siempre y cuando todos los identificadores hagan referencia a variables existentes y campos de los tipos adecuados):

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

El depurador en modo mixto también resuelve tales expresiones de forma diferente. Todas las operaciones de acceso a miembros buscan solamente campos que son parte directamente del objeto (como una entrada en su __dict__ o __slots__, o un campo de un struct nativo que se expone a Python mediante tp_members), y omite cualquier __getattr__, __getattribute__ o lógica de descriptor. De igual forma, todas las operaciones de indexación omiten __getitem__ y acceden directamente a la estructuras de datos internas de las colecciones.

Para mantener la coherencia, este esquema de resolución de nombres se usa para todas las expresiones que coinciden con las restricciones para la evaluación de expresiones limitadas. Este esquema se aplica independientemente de si se permiten expresiones arbitrarias en el punto de detención actual. Para forzar la semántica de Python apropiada cuando un evaluador completo está disponible, encierre la expresión entre paréntesis:

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