Depurar Python e C++ juntos no Visual Studio

A maioria dos depuradores Python regulares suportam somente a depuração de código Python, mas é prática comum para desenvolvedores usar Python com C ou C++. Alguns cenários que usam código misto são aplicativos que exigem alta performance ou a capacidade de invocar diretamente APIs de plataforma são frequentemente codificados em Python e C ou C++.

O Visual Studio fornece depuração de modo misto integrada e simultânea para o Python e código C/C++ nativo. O suporte está disponível quando você seleciona a opção de ferramentas de desenvolvimento nativo Python para a carga de trabalho Python Development no instalador do Visual Studio:

Captura de tela mostrando a opção de ferramentas de desenvolvimento nativas do Python selecionada no Instalador do Visual Studio.

Neste artigo, você explora como trabalhar com estes recursos de depuração de modo misto:

  • Pilhas de chamada combinadas
  • Etapa entre o Python e o código nativo
  • Pontos de interrupção nos dois tipos de código
  • Confira as representações de Python de objetos em quadros nativos e vice-versa
  • Depuração dentro do contexto do projeto do Python ou C++

Captura de tela mostrando um exemplo de depuração de modo misto para código em Python e C++ no Visual Studio.

Pré-requisitos

  • Visual Studio 2017 e posterior. A depuração de modo misto não está disponível nas Ferramentas Python para Visual Studio 1.x no Visual Studio 2015 e anteriores.

  • Ter o Visual Studio instalado e compatível com cargas de trabalho do Python. Para obter mais informações, confira Instalar o suporte ao Python no Visual Studio.

Habilitar a depuração de modo misto em um projeto do Python

Os passos a seguir descrevem como habilitar a depuração de modo misto em um projeto Python:

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto Python e selecione Propriedades.

  2. No painel Propriedades, selecione a guia Depurar e a opção Depurar>Habilitar a depuração de código nativo:

    Captura de tela mostrando como definir a propriedade Habilitar depuração de código nativo no Visual Studio.

    Essa opção habilita o modo misto em todas as sessões de depuração.

    Dica

    Quando você habilitar a depuração de código nativo, a janela de Saída do Python poderá fechar imediatamente depois que o programa terminar sem pausar ou apresentar o prompt Pressione qualquer tecla para continuar. Para forçar a pausa e o prompt depois de habilitar a depuração de código nativo, adicione o argumento -i ao campo Executar>Argumentos do interpretador na guia Depurar. Esse argumento coloca o interpretador Python no modo interativo após a execução do código. O programa aguarda você selecionar Ctrl+Z+Enter para fechar a janela.

  3. Selecione Arquivo>Salvar (ou Ctrl+S) para salvar as alterações de propriedade.

  4. Para anexar o depurador de modo misto a um processo existente, selecione Depurar>Anexar ao Processo. Uma caixa de diálogo aparece.

    1. Na caixa de diálogo Anexar ao Processo, selecione o processo protegido na lista.

    2. Para o campo Anexar a, use a opção Selecionar para abrir a caixa de diálogo Selecionar tipo de código.

    3. Na caixa de diálogo Selecionar tipo de código, escolha a opção Depurar estes tipos de código.

    4. Na lista, marque a caixa de seleção Python (nativo) e selecione OK:

      Captura de tela mostrando como selecionar o tipo de código Python (nativo) para depuração no Visual Studio.

    5. Escolha Anexar para iniciar o depurador.

    As configurações do tipo de código são persistentes. Se você quiser desabilitar a depuração de modo misto e anexar a um processo diferente posteriormente, desmarque a caixa de seleção do tipo de código Python (nativo) e marque a caixa de seleção Tipo de código Nativo.

    Você pode selecionar outros tipos de código além da opção Nativo. Por exemplo, se um aplicativo gerenciado hospedar o CPython, que por sua vez usa módulos de extensão nativos, e você quiser depurar os três projetos de código, marque as caixas de seleção Python, Nativo e Gerenciado. Essa abordagem traz uma experiência de depuração unificada, incluindo pilhas de chamadas combinadas e etapas entre os três tempos de execução.

Como trabalhar com ambientes virtuais

Ao usar esse método de depuração de modo misto para ambientes virtuais (venvs), o Python para Windows usa um arquivo stub python.exe para venvs que o Visual Studio localiza e carrega como um subprocesso.

  • Para o Python 3.8 e posterior, o modo misto não dá suporte à depuração de vários processos. Quando você inicia a sessão da depuração, o subprocesso de stub é depurado em vez do aplicativo. Em cenários de anexação, a solução alternativa é anexar ao arquivo python.exe correto. Quando inicia o aplicativo com depuração (como pelo atalho de teclado F5), você pode criar seu venv usando o comando C:\Python310-64\python.exe -m venv venv --symlinks. No comando, coloque sua versão preferida do Python. Por padrão, somente Administradores podem criar links simbólicos no Windows.

  • Para versões do Python antes da 3.8, a depuração de modo misto deve funcionar conforme o esperado com venvs.

A execução em um ambiente global não causa esses problemas para nenhuma versão do Python.

Instalar símbolos Python

Ao iniciar a depuração no modo misto pela primeira vez, você poderá ver uma caixa de diálogo Símbolos Obrigatórios do Python. Você precisa instalar os símbolos apenas uma vez para qualquer ambiente do Python. Os símbolos serão incluídos automaticamente se você instalar o suporte do Python por meio do Instalador do Visual Studio (Visual Studio 2017 e posterior). Para mais informações, consulte Instalar símbolos de depuração para interpretadores Python no Visual Studio.

Acessar o código-fonte Python

Você pode disponibilizar o código-fonte do Python padrão durante a depuração.

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

  2. Faça o download do arquivo de código-fonte Python apropriado para sua versão e extraia o código para uma pasta.

  3. Quando o Visual Studio solicita o local do código-fonte Python, indique para os arquivos específicos na pasta de extração.

Habilitar a depuração de modo misto em um projeto do C/C++

O Visual Studio 2017 versão 15.5 e posterior dá suporte à depuração de modo misto de um projeto C/C++. Um exemplo desse uso é quando você quer incorporar o Python em outro aplicativo, conforme descrito em python.org.

Os passos a seguir descrevem como habilitar a depuração de modo misto para um projeto C/C++:

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto C/C++ e selecione Propriedades.

  2. No painel Páginas de Propriedades, selecione a guia Propriedades de Configuração>Depuração.

  3. Expanda o menu suspenso para a opção Depurador para iniciar e selecione Python/Depuração Nativa.

    Captura de tela mostrando como selecionar a opção Depuração de Python Nativo para um projeto em C/C++ no Visual Studio.

    Observação

    Se você não tiver a opção Python/Depuração Nativa, primeiro instale as ferramentas de desenvolvimento nativas do Python usando o instalador do Visual Studio. A opção de depuração nativa está disponível na carga de trabalho para desenvolvimento do Python. Para obter mais informações, confira Instalar o suporte ao Python no Visual Studio.

  4. Selecione Ok para salvar as alterações.

Depurar o iniciador de programas

Ao usar este método, não é possível depurar o Launcher de programa py.exe porque ele gera um subprocesso filho python.exe. O depurador não é anexado ao subprocesso. Nesse cenário, a solução alternativa é iniciar o programa python.exe diretamente com argumentos, da seguinte maneira:

  1. No painel Páginas de propriedade para o projeto C/C++, acesse a guia Propriedades de configuração>Depuração.

  2. Na opção Comando, especifique o caminho completo para o arquivo de programa python.exe.

  3. Especifique os argumentos desejados no campo Argumentos de comando.

Anexar o depurador de modo misto

Para o Visual Studio 2017 versão 15.4 e anteriores, a depuração direta de modo misto é ativada somente ao iniciar um projeto Python no Visual Studio. O suporte é limitado porque os projetos C/C++ usam somente o depurador nativo.

Para este cenário, a solução alternativa é anexar o depurador separadamente:

  1. Inicie o projeto C++ sem depurar selecionando Depurar>Iniciar sem depurar ou use o atalho do teclado Ctrl+F5.

  2. Para anexar o depurador de modo misto a um processo existente, selecione Depurar>Anexar ao Processo. Uma caixa de diálogo aparece.

    1. Na caixa de diálogo Anexar ao Processo, selecione o processo protegido na lista.

    2. Para o campo Anexar a, use a opção Selecionar para abrir a caixa de diálogo Selecionar tipo de código.

    3. Na caixa de diálogo Selecionar tipo de código, escolha a opção Depurar estes tipos de código.

    4. Na lista, marque a caixa de seleção Python (nativo) e selecione OK.

    5. Escolha Anexar para iniciar o depurador.

Dica

Você pode introduzir uma pausa ou atraso no aplicativo C++ para garantir que ele não chame o código Python que você quer depurar antes de anexar o depurador.

Explore recursos específicos ao modo misto

O Visual Studio tem vários recursos de depuração de modo misto para facilitar a depuração de seu aplicativo:

Usar uma pilha de chamadas combinada

A janela Pilha de Chamadas mostra os registros de ativação nativo e do Python intercalados, com transições marcadas entre os dois:

Captura de tela da janela de pilha de chamadas agrupadas com depuração de modo misto no Visual Studio.

  • Para fazer as transições aparecerem como [Código Externo] sem especificar a direção da transição, defina a opção Ferramentas>Opções>Depuração>Geral>Habilitar Apenas Meu Código.

  • Para tornar ativo qualquer quadro de chamada, clique duas vezes no quadro. Essa ação abre também o código-fonte correspondente, se possível. Se o código-fonte não estiver disponível, o quadro ainda ficará ativo e as variáveis locais poderão ser inspecionadas.

Etapa entre o Python e o código nativo

O Visual Studio oferece os comandos Intervir (F11) ou Sair (Shift+F11) para permitir que o depurador de modo misto manipule corretamente das alterações entre os tipos de código.

  • Quando o Python chama um método de um tipo implementado no C, a intervenção em uma chamada a esse método é interrompida no início da função nativa que implementa o método.

  • Este mesmo comportamento ocorre quando o código nativo chama uma função de API do Python, isso resulta na invocação do código do Python. Intervir em uma chamada para PyObject_CallObject em um valor de função que foi originalmente definido no Python é interrompida no início da função do Python.

  • Também há suporte para a intervenção do Python para nativo em funções nativas invocadas do Python por meio de ctypes.

Usar exibição de valores PyObject no código nativo

Quando um quadro nativo (C ou C++) está ativo, suas variáveis locais são mostradas na janela Locais do depurador. os módulos de extensão nativos do Python, muitas dessas variáveis são do tipo PyObject (que é um typedef de _object), ou alguns outros tipos fundamentais do Python. Na depuração de modo misto, esses valores apresentam outro nó filho rotulado [exibição do Python].

  • Para ver a representação Python da variável, expanda o nó. A exibição das variáveis é idêntica ao que aparece se uma variável local que faz referência ao mesmo objeto estiver presente em um quadro Python. Os filhos desse nó são editáveis.

    Captura de tela mostrando o Modo de Exibição em Python na janela Locais no Visual Studio.

  • Para desabilitar esse recurso, clique com o botão direito do mouse em qualquer lugar da janela Locais e ative/desative a opção de menu Python>Mostrar Nós de Exibição do Python:

    Captura de tela mostrando como habilitar a opção Mostrar Nodes de Exibição em Python na janela Locais.

Tipos C que mostram nós de visualização Python

Os seguintes tipos C mostram nós de [Exibição do Python] (se estiverem habilitados):

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

A [exibição do Python] não é mostrada automaticamente para tipos criados por sua própria conta. Quando você cria extensões para Python 3.x, essa falha geralmente não é um problema. Qualquer objeto, em última análise, tem um campo ob_base de um dos tipos C listados, o que faz com que [visualização Python] apareça.

Exibir valores nativos no código Python

Você pode habilitar uma [Exibição do C++] para valores nativos na janela Locais quando um quadro do Python estiver ativo. Esse recurso não é habilitado por padrão.

  • Para ativar o recurso, clique com o botão direito na janela Locais e defina a opção de menu Python>Mostrar Nós de Exibição C++.

    Captura de tela mostrando como habilitar as opções Mostrar Nodes de Exibição em C++ na janela Locais.

  • O nó [Exibição do C++] fornece uma representação da estrutura subjacente do C/C++ de um valor, idêntico ao que você veria em um quadro nativo. Ele mostra uma instância de _longobject (para a qual PyLongObject é um typedef) de um inteiro longo do Python e tenta inferir tipos para as classes nativas criadas por conta própria. Os filhos desse nó são editáveis.

    Captura de tela mostrando o modo de exibição em C++ na janela Locais no Visual Studio.

Se um campo filho de um objeto for do tipo PyObject, ou outro tipo compatível, ele terá um nó de representação [Exibição Python] (se essas representações estiverem habilitadas). Esse comportamento possibilita navegar em gráficos de objetos onde os links não são diretamente expostos ao Python.

Ao contrário dos nós [exibição do Python], que usam metadados de objeto do Python para determinar o tipo do objeto, não há nenhum mecanismo similarmente confiável para a [exibição do C++]. Em termos gerais, considerando um valor do Python (ou seja, uma referência PyObject), não é possível determinar com confiança qual estrutura do C/C++ está dando suporte a ele. O depurador de modo misto tenta adivinhar esse tipo, observando diversos campos do tipo de objeto (como o PyTypeObject referenciado por seu campo ob_type) que têm tipos de ponteiro de função. Se um desses ponteiros de função referenciar uma função que pode ser resolvida e essa função tiver um parâmetro self com um tipo mais específico que PyObject*, esse tipo será considerado como o tipo de suporte.

Considere o exemplo a seguir, em que o valor ob_type->tp_init de um determinado objeto aponta para esta função:

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

Nesse caso, o depurador poderá deduzir corretamente que o tipo C do objeto é FobObject. Se o depurador não conseguir determinar um tipo mais preciso de tp_init, ele seguirá para outros campos. Se não for possível deduzir o tipo de nenhum desses campos, o nó [exibição do C++] apresentará o objeto como uma instância de PyObject.

Para obter sempre uma representação útil de tipos criados personalizados, é melhor registrar, pelo menos, uma função especial ao registrar o tipo e usar um parâmetro self fortemente tipado. A maioria dos tipos cumpre este requisito naturalmente. Para outros tipos, a inspeção tp_init é geralmente a entrada mais conveniente para usar para este fim. Uma implementação fictícia de tp_init de um tipo que está presente exclusivamente para habilitar a inferência de tipos do depurador pode apenas retornar zero imediatamente, como no exemplo anterior.

Rever as diferenças da depuração padrão do Python

O depurador de modo misto é diferente do depurador Python padrão. Ele introduz alguns recursos extras, mas não tem alguns recursos relacionados ao Python, como segue:

  • Funcionalidades sem suporte incluem pontos de interrupção condicionais, a janela Interativa de Depuração e depuração remota multiplataforma.
  • A Janela Imediata: disponível, mas com um subconjunto limitado de sua funcionalidade, incluindo todas as limitações listadas nesta seção.
  • Versões do Python com suporte incluem somente CPython 2.7 e 3.3+.
  • Para usar o Python com o Shell do Visual Studio (por exemplo, se você instalá-lo com o instalador integrado), o Visual Studio não consegue abrir projetos C++. Como resultado, a experiência de edição de arquivos C++ é a de um editor de texto básico somente. No entanto, há suporte completo para a depuração do C/C++ e a depuração de modo misto no Shell com código-fonte, execução em etapas em código nativo e avaliação de expressão do C++ nas janelas do depurador.
  • Ao exibir objetos do Python nas janelas Locais e Inspeção da ferramenta do depurador, o depurador de modo misto mostra somente a estrutura dos objetos. Ele não avalia propriedades automaticamente nem mostra atributos computados. Para coleções, ele mostra apenas os elementos de tipos de coleção interna (tuple, list, dict, set). Os tipos de coleção personalizada não são visualizados como coleções, a menos que sejam herdados de algum tipo de coleção interna.
  • A avaliação de expressão é tratada como descrito na seção a seguir.

Usar avaliação de expressão

O depurador padrão do Python permite a avaliação de expressões arbitrárias do Python nas janelas Inspeção e Imediata quando o processo depurado está em pausa em qualquer ponto do código, desde que ele não esteja bloqueado em uma operação de E/S ou em outra chamada do sistema semelhante. Na depuração de modo misto, as expressões arbitrárias podem ser avaliadas somente quando interrompidas no código do Python, depois de um ponto de interrupção ou intervindo no código. As expressões podem ser avaliadas apenas no thread em que o ponto de interrupção ou a operação de intervenção ocorreu.

Quando o depurador pára em código nativo ou em código Python onde as condições descritas não se aplicam, como depois uma operação de "sair" ou em um thread diferente. A avaliação de expressões é limitada ao acesso a variáveis locais e globais no escopo do quadro selecionado atualmente, acessando os seus campos e indexando os tipos de coleção integrados com literais. Por exemplo, a seguinte expressão pode ser avaliada em qualquer contexto (desde que todos os identificadores refiram-se a variáveis e a campos existentes dos tipos apropriados):

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

O depurador de modo misto também resolve essas expressões de outra forma. Todas as operações de acesso a membro pesquisam somente os campos que fazem parte diretamente do objeto (como uma entrada em seu __dict__ ou __slots__, ou um campo de um struct nativo que é exposto ao Python por meio de tp_members) e ignoram qualquer __getattr__, __getattribute__ ou lógica do descritor. Da mesma forma, todas as operações de indexação ignoram __getitem__ e acessam as estruturas de dados internas das coleções diretamente.

Por uma questão de consistência, esse esquema de resolução de nomes é usado para todas as expressões que correspondam às restrições para a avaliação de expressão limitada. Esse esquema se aplica independentemente de expressões arbitrárias serem permitidas no ponto de parada atual. Para forçar a semântica correta do Python quando um avaliador completo está disponível, coloque a expressão entre parênteses:

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