Eseguire il debug di Python e C++ insieme in Visual Studio

La maggior parte dei normali debugger Python supporta solo il debug del codice Python, ma è pratica comune per gli sviluppatori usare Python con C o C++. Alcuni scenari che usano codice misto sono applicazioni che richiedono prestazioni elevate o la possibilità di richiamare direttamente le API della piattaforma vengono spesso codificate in Python e C o C++.

Visual Studio offre il debug in modalità mista integrata e simultanea per il codice C/C++ nativo e Python. Il supporto è disponibile quando si seleziona l'opzione Strumenti di sviluppo nativo Python per il carico di lavoro Sviluppo Python nel programma di installazione di Visual Studio:

Screenshot che mostra l'opzione Strumenti di sviluppo nativo Python selezionata nella Programma di installazione di Visual Studio.

In questo articolo viene illustrato come usare le funzionalità di debug in modalità mista seguenti:

  • Stack di chiamate combinato
  • Passaggio tra codice Python e codice nativo
  • Punti di interruzione in entrambi i tipi di codice
  • Visualizzare le rappresentazioni Python degli oggetti in frame nativi e viceversa
  • Debug all'interno del contesto del progetto Python o del progetto C++

Screenshot che mostra un esempio di debug in modalità mista per il codice Python e C++ in Visual Studio.

Prerequisiti

  • Visual Studio 2017 e versioni successive. Il debug in modalità mista non è disponibile con Python Tools per Visual Studio 1.x in Visual Studio 2015 e versioni precedenti.

  • Visual Studio installato con il supporto per i carichi di lavoro Python. Per altre informazioni, vedere Installare il supporto python in Visual Studio.

Abilitare il debug in modalità mista in un progetto Python

I passaggi seguenti descrivono come abilitare il debug in modalità mista in un progetto Python:

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto Python e scegliere Proprietà.

  2. Nel riquadro Proprietà selezionare la scheda Debug e quindi selezionare l'opzione Debug>Abilita debug del codice nativo:

    Screenshot che mostra come impostare la proprietà Abilita debug del codice nativo in Visual Studio.

    Questa opzione abilita la modalità mista per tutte le sessioni di debug.

    Suggerimento

    Quando si abilita il debug del codice nativo, la finestra di output di Python potrebbe chiudersi immediatamente dopo il completamento del programma senza sospendere e visualizzare il tasto Press any key per continuare . Per forzare la sospensione e la richiesta dopo aver abilitato il debug del codice nativo, aggiungere l'argomento al campo Argomenti dell'interprete -idi esecuzione>nella scheda Debug. Questo argomento inserisce l'interprete Python in modalità interattiva dopo l'esecuzione del codice. Il programma attende di selezionare CTRL+Z+INVIO per chiudere la finestra.

  3. Selezionare Salva file>(o CTRL+S) per salvare le modifiche alle proprietà.

  4. Per collegare il debugger in modalità mista a un processo esistente, selezionare Debug>Connetti a processo. Verrà visualizzata una finestra di dialogo.

    1. Nella finestra di dialogo Connetti a processo selezionare il processo appropriato dall'elenco.

    2. Per il campo Connetti a , usare l'opzione Seleziona per aprire la finestra di dialogo Seleziona tipo di codice.

    3. Nella finestra di dialogo Seleziona tipo di codice scegliere l'opzione Debug di questi tipi di codice.

    4. Nell'elenco selezionare la casella di controllo Python (nativa) e selezionare OK:

      Screenshot che mostra come selezionare il tipo di codice Python (nativo) per il debug in Visual Studio.

    5. Selezionare Connetti per avviare il debugger.

    Le impostazioni del tipo di codice sono persistenti. Se si vuole disabilitare il debug in modalità mista e collegarsi a un processo diverso in un secondo momento, deselezionare la casella di controllo Tipo di codice Python (nativo) e selezionare la casella di controllo Tipo di codice nativo .

    È possibile selezionare altri tipi di codice oltre a o anziché l'opzione Nativa . Ad esempio, se un'applicazione gestita ospita CPython, che a sua volta usa moduli di estensione nativi e si vuole eseguire il debug di tutti e tre i progetti di codice, selezionare le caselle di controllo Python, Native e Managed . Questo approccio offre un'esperienza di debug unificata, inclusi gli stack di chiamate combinati e l'esecuzione di istruzioni tra tutti e tre i runtime.

Usare ambienti virtuali

Quando si usa questo metodo di debug in modalità mista per gli ambienti virtuali (venvs), Python per Windows usa un python.exe file stub per venvs che Visual Studio trova e carica come sottoprocesso.

  • Per Python 3.8 e versioni successive, la modalità mista non supporta il debug multiprocesso. Quando si avvia la sessione di debug, il sottoprocesso stub viene sottoposto a debug anziché all'applicazione. Per gli scenari di collegamento, la soluzione alternativa consiste nel allegare al file corretto python.exe . Quando si avvia l'applicazione con il debug ,ad esempio tramite il tasto di scelta rapida F5 , è possibile creare il venv usando il comando C:\Python310-64\python.exe -m venv venv --symlinks. Nel comando inserire la versione preferita di Python. Per impostazione predefinita, solo gli amministratori possono creare collegamenti simbolici in Windows.

  • Per le versioni di Python precedenti alla 3.8, il debug in modalità mista dovrebbe funzionare come previsto con venvs.

L'esecuzione in un ambiente globale non causa questi problemi per alcuna versione di Python.

Installare i simboli Python

Quando si avvia il debug in modalità mista per la prima volta, è possibile che venga visualizzata una finestra di dialogo Simboli Python necessari . È necessario installare i simboli una sola volta per qualsiasi ambiente Python specifico. I simboli vengono inclusi automaticamente se si installa il supporto python tramite il Programma di installazione di Visual Studio (Visual Studio 2017 e versioni successive). Per altre informazioni, vedere Installare i simboli di debug per gli interpreti Python in Visual Studio.

Accedere al codice sorgente Python

È possibile rendere disponibile il codice sorgente per Python standard durante il debug.

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

  2. Scaricare l'archivio del codice sorgente Python appropriato per la versione e estrarre il codice in una cartella.

  3. Quando Visual Studio richiede il percorso del codice sorgente Python, puntare ai file specifici nella cartella di estrazione.

Abilitare il debug in modalità mista in un progetto C/C++

Visual Studio 2017 versione 15.5 e successive supporta il debug in modalità mista da un progetto C/C++. Un esempio di questo utilizzo è quando si vuole incorporare Python in un'altra applicazione, come descritto in python.org.

I passaggi seguenti descrivono come abilitare il debug in modalità mista per un progetto C/C++:

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto C/C++ e scegliere Proprietà.

  2. Nel riquadro Pagine delle proprietà selezionare la scheda Debug proprietà>di configurazione.

  3. Espandere il menu a discesa per l'opzione Debugger da avviare e selezionare Debug python/nativo.

    Screenshot che mostra come selezionare l'opzione Debug nativo Python per un progetto C/C++ in Visual Studio.

    Nota

    Se non viene visualizzata l'opzione Debug python/nativo, è prima necessario installare gli strumenti di sviluppo nativi Python usando il Programma di installazione di Visual Studio. L'opzione di debug nativa è disponibile nel carico di lavoro Sviluppo Python. Per altre informazioni, vedere Installare il supporto python in Visual Studio.

  4. Selezionare OK per salvare le modifiche.

Eseguire il debug dell'utilità di avvio del programma

Quando si usa questo metodo, non è possibile eseguire il debug dell'utilità di avvio del py.exe programma perché genera un sottoprocesso figlio python.exe . Il debugger non è collegato al sottoprocesso. Per questo scenario, la soluzione alternativa consiste nell'avviare il python.exe programma direttamente con argomenti, come indicato di seguito:

  1. Nel riquadro Pagine delle proprietà per il progetto C/C++ passare alla scheda Debug delle proprietà>di configurazione.

  2. Per l'opzione Comando specificare il percorso completo del file di python.exe programma.

  3. Specificare gli argomenti desiderati nel campo Argomenti comando .

Collegare il debugger in modalità mista

Per Visual Studio 2017 versione 15.4 e precedenti, il debug in modalità mista diretta è abilitato solo quando si avvia un progetto Python in Visual Studio. Il supporto è limitato perché i progetti C/C++ usano solo il debugger nativo.

Per questo scenario, la soluzione alternativa consiste nel collegare il debugger separatamente:

  1. Avviare il progetto C++ senza eseguire il debug selezionando Avvia>debug senza eseguire debug o usare i tasti di scelta rapida CTRL+F5.

  2. Per collegare il debugger in modalità mista a un processo esistente, selezionare Debug>Connetti a processo. Verrà visualizzata una finestra di dialogo.

    1. Nella finestra di dialogo Connetti a processo selezionare il processo appropriato dall'elenco.

    2. Per il campo Connetti a , usare l'opzione Seleziona per aprire la finestra di dialogo Seleziona tipo di codice.

    3. Nella finestra di dialogo Seleziona tipo di codice scegliere l'opzione Debug di questi tipi di codice.

    4. Nell'elenco selezionare la casella di controllo Python e selezionare OK.

    5. Selezionare Connetti per avviare il debugger.

Suggerimento

È possibile aggiungere una pausa o un ritardo nell'applicazione C++ per assicurarsi che non chiami il codice Python di cui si vuole eseguire il debug prima di collegare il debugger.

Esplorare le funzionalità specifiche della modalità mista

Visual Studio offre diverse funzionalità di debug in modalità mista per semplificare il debug dell'applicazione:

Usare uno stack di chiamate combinato

La finestra Stack di chiamate mostra sia gli stack frame nativi che di Python con interleave, con le transizioni contrassegnate tra i due:

Screenshot della finestra combinata dello stack di chiamate con il debug in modalità mista in Visual Studio.

  • Per eseguire transizioni come [Codice esterno] senza specificare la direzione della transizione, impostare l'opzione Strumenti>Opzioni>debug>generale>Abilita Just My Code.

  • Per attivare qualsiasi fotogramma di chiamata, fare doppio clic sul fotogramma. Questa azione apre anche il codice sorgente corrispondente, se possibile. Se il codice sorgente non è disponibile, il frame viene ancora reso attivo e le variabili locali possono essere esaminate.

Passaggio tra codice Python e codice nativo

Visual Studio fornisce i comandi Esegui istruzione (F11) o Esci (Maiusc+F11) per consentire al debugger in modalità mista di gestire correttamente le modifiche tra i tipi di codice.

  • Quando Python chiama un metodo di un tipo implementato in C, l'esecuzione di istruzioni su una chiamata a tale metodo si arresta all'inizio della funzione nativa che implementa il metodo .

  • Questo stesso comportamento si verifica quando il codice nativo chiama una funzione API Python che comporta la chiamata al codice Python. L'esecuzione di una chiamata a PyObject_CallObject su un valore di funzione originariamente definito in Python si arresta all'inizio della funzione Python.

  • Il passaggio da codice Python a codice nativo è supportato anche per le funzioni native richiamate da Python tramite ctypes.

Usare la visualizzazione dei valori PyObject nel codice nativo

Quando è attivo un frame nativo (C o C++), le relative variabili locali vengono visualizzate nella finestra Variabili locali del debugger. Nei moduli di estensione Python nativi, molte di queste variabili sono di tipo PyObject (ovvero un typedef per _object) o altri tipi di Python fondamentali. Nel debug in modalità mista, questi valori presentano un altro nodo figlio con etichetta [visualizzazione Python].

  • Per visualizzare la rappresentazione Python della variabile, espandere il nodo. La visualizzazione delle variabili è identica a quella visualizzata se una variabile locale che fa riferimento allo stesso oggetto è presente in un frame Python. Gli elementi figlio di questo nodo sono modificabili.

    Screenshot che mostra la visualizzazione Python nella finestra Variabili locali in Visual Studio.

  • Per disabilitare questa funzionalità, fare clic con il pulsante destro del mouse in qualsiasi punto della finestra Variabili locali e disattivare l'opzione di menu Python>Mostra nodi di visualizzazione Python:

    Screenshot che mostra come abilitare l'opzione Mostra nodi visualizzazione Python per la finestra Variabili locali.

Tipi C che mostrano i nodi di visualizzazione Python

I tipi C seguenti mostrano i nodi [visualizzazione Python] , se abilitati:

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

[Visualizzazione Python] non viene visualizzato automaticamente per i tipi creati manualmente. Quando si creano estensioni per Python 3.x, questa mancanza non è in genere un problema. Qualsiasi oggetto ha in definitiva un ob_base campo di uno dei tipi C elencati, che causa la visualizzazione [Python] da visualizzare.

Visualizzare i valori nativi nel codice Python

È possibile abilitare una [visualizzazione C++] per i valori nativi nella finestra Variabili locali quando un frame Python è attivo. Questa funzionalità non è abilitata per impostazione predefinita.

  • Per abilitare la funzionalità, fare clic con il pulsante destro del mouse nella finestra Variabili locali e impostare l'opzione >di menu Python Show C++ View Nodes (Mostra nodi visualizzazione C++).

    Screenshot che mostra come abilitare le opzioni Mostra nodi visualizzazione C++ per la finestra Variabili locali.

  • Il nodo [visualizzazione C++] fornisce una rappresentazione della struttura C/C++ sottostante per un valore, identico a quello visualizzato in un frame nativo. Mostra un'istanza di _longobject (per cui PyLongObject è un typedef) per un intero lungo Python e tenta di dedurre i tipi per le classi native create dall'utente. Gli elementi figlio di questo nodo sono modificabili.

    Screenshot che mostra la visualizzazione C++ nella finestra Variabili locali in Visual Studio.

Se un campo figlio di un oggetto è di tipo PyObjecto un altro tipo supportato, ha un nodo di rappresentazione [visualizzazione Python] (se tali rappresentazioni sono abilitate). Questo comportamento consente di esplorare gli oggetti grafici in cui i collegamenti non sono esposti direttamente a Python.

Diversamente dai nodi [Visualizzazione Python] che usano i metadati degli oggetti Python per determinare il tipo dell'oggetto, non esiste un meccanismo affidabile simile per i nodi [Visualizzazione C++]. In termini generali, dato un valore Python, ovvero un riferimento a PyObject, non è possibile determinare in modo affidabile la struttura C/C++ sottostante. Il debugger in modalità mista tenta di indovinare il tipo esaminando vari campi del tipo dell'oggetto (ad esempio il PyTypeObject riferimento dal relativo ob_type campo) che hanno tipi di puntatore a funzione. Se uno di questi puntatori di funzione fa riferimento a una funzione che può essere risolta e tale funzione ha un parametro con un self tipo più specifico di PyObject*, si presuppone che tale tipo sia il tipo di supporto.

Si consideri l'esempio seguente, dove il ob_type->tp_init valore per di un determinato oggetto punta alla funzione seguente:

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

In questo caso, il debugger può dedurre correttamente che il tipo C dell'oggetto è FobObject. Se il debugger non riesce a determinare un tipo più preciso da tp_init, passa ad altri campi. Se non è in grado di dedurre il tipo da uno qualsiasi di tali campi, il nodo [Visualizzazione C++] presenta l'oggetto come istanza di PyObject.

Per ottenere sempre una rappresentazione utile per i tipi personalizzati creati, è consigliabile registrare almeno una funzione speciale quando si registra il tipo e usare un parametro self fortemente tipizzato. La maggior parte dei tipi soddisfa naturalmente tale requisito. Per altri tipi, l'ispezione tp_init è in genere la voce più comoda da usare a questo scopo. Un'implementazione fittizia di tp_init per un tipo presente esclusivamente per abilitare l'inferenza del tipo di debugger può restituire immediatamente zero, come nell'esempio precedente.

Esaminare le differenze rispetto al debug Python standard

Il debugger in modalità mista è distinto dal debugger Python standard. Introduce alcune funzionalità aggiuntive, ma non include alcune funzionalità correlate a Python, come indicato di seguito:

  • Le funzionalità non supportate includono punti di interruzione condizionali, la finestra Debug interattivo e il debug remoto multipiattaforma.
  • La finestra Immediata è disponibile, ma con un subset limitato delle relative funzionalità, incluse tutte le limitazioni elencate in questa sezione.
  • Le versioni di Python supportate includono solo CPython 2.7 e 3.3+.
  • Per usare Python con Visual Studio Shell (ad esempio, se si installa con il programma di installazione integrato), Visual Studio non è in grado di aprire progetti C++. Di conseguenza, l'esperienza di modifica per i file C++ è solo quella di un editor di testo di base. Il debug di C/C++ e il debug in modalità mista sono tuttavia supporti completamente nella shell con il codice sorgente, l'esecuzione istruzione per istruzione del codice nativo e la valutazione delle espressioni C++ nelle finestre del debugger.
  • Quando si visualizzano gli oggetti Python nelle finestre degli strumenti Variabili locali e Espressioni di controllo , il debugger in modalità mista mostra solo la struttura degli oggetti. Non valuta automaticamente le proprietà o mostra gli attributi calcolati. Per le raccolte, mostra solo gli elementi per i tipi di raccolta predefiniti (tuple, list, dict, set). I tipi di raccolta personalizzati non vengono visualizzati come raccolte, a meno che non vengano ereditati da un tipo di raccolta predefinito.
  • La valutazione delle espressioni viene gestita come descritto nella sezione seguente.

Usare la valutazione delle espressioni

Il debugger Python standard consente la valutazione delle espressioni Python arbitrarie nelle finestre Espressioni di controllo e controllo immediato quando il processo sottoposto a debug viene sospeso in qualsiasi punto del codice, purché non venga bloccato in un'operazione di I/O o in un'altra chiamata di sistema simile. Durante il debug in modalità mista, possono essere valutate espressioni arbitrarie solo quando interrotte all'interno del codice Python, dopo un punto di interruzione o durante l'esecuzione di istruzioni nel codice. Le espressioni possono essere valutate solo per il thread in cui è presente il punto di interruzione o viene eseguita l'operazione di debug passo a passo.

Quando il debugger si arresta nel codice nativo o nel codice Python in cui le condizioni descritte non si applicano, ad esempio dopo un'operazione di passaggio o in un thread diverso. La valutazione delle espressioni è limitata all'accesso alle variabili locali e globali nell'ambito del frame attualmente selezionato, all'accesso ai relativi campi e all'indicizzazione dei tipi di raccolta predefiniti con valori letterali. Ad esempio, l'espressione seguente può essere valutata in qualsiasi contesto (purché tutti gli identificatori facciano riferimento a variabili e campi esistenti di tipi appropriati):

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

Il debugger in modalità mista risolve anche queste espressioni in modo diverso. Tutte le operazioni di accesso ai membri cercano solo i campi che fanno direttamente parte dell'oggetto (ad esempio una voce nel relativo __dict__ o __slots__o un campo di uno struct nativo esposto a Python tramite tp_members) e ignorano qualsiasi __getattr__logica di descrittore , __getattribute__o . In modo analogo, tutte le operazioni di indicizzazione ignorano __getitem__ e accedono direttamente alle strutture dei dati interne delle raccolte.

Per garantire la coerenza, questo schema di risoluzione dei nomi viene usato per tutte le espressioni che corrispondono ai vincoli per la valutazione limitata delle espressioni. Questo schema viene applicato indipendentemente dal fatto che le espressioni arbitrarie siano consentite nel punto di arresto corrente. Per forzare la semantica di Python appropriata quando è disponibile un analizzatore completo, racchiudere l'espressione tra parentesi:

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