Visual Studio で Python と C++ を一緒にデバッグする

通常の Python デバッガーの大半は Python コードのデバッグのみをサポートしていますが、開発者にとっては C または C++ と一緒に Python を使用するのが一般的です。 混合コードを使用するシナリオには、ハイ パフォーマンス、またはプラットフォーム API を直接呼び出す機能が必要なアプリケーションや、Python と C または C++ でコーディングされることが多いアプリケーションなどがあります。

Visual Studio には、Python コードとネイティブ C/C++ コード向けに統合された同時混合モード デバッグが用意されています。 サポートを利用するには、Visual Studio インストーラーで [Python 開発] ワークロードの [Python ネイティブ開発ツール] オプションを選択します。

Visual Studio インストーラーで選択されている Python ネイティブ開発ツール オプションを示すスクリーンショット。

この記事では、次の混合モード デバッグ機能の操作方法について説明します。

  • 結合された呼び出し履歴
  • Python とネイティブ コード間でのステップ実行
  • 両方の種類のコード内のブレークポイント
  • Python のオブジェクト表現をネイティブ フレームに表示する機能 (逆も可能)
  • Python プロジェクトまたは C++ プロジェクトのコンテキスト内でのデバッグ

Visual Studio での Python と C++ コードの混合モード デバッグの例を示すスクリーンショット。

前提条件

  • Visual Studio 2017 以降。 混合モード デバッグは、Visual Studio 2015 以前の Python Tools for Visual Studio 1.x では使用できません。

  • Python ワークロードをサポートする Visual Studio がインストールされていること。 詳細については、「Visual Studio での Python サポートのインストール」をご覧ください。

Python プロジェクトでの混合モード デバッグの有効化

次に、Python プロジェクトで混合モード デバッグを有効にする手順を説明します。

  1. ソリューション エクスプローラーで Python プロジェクトを右クリックし、[プロパティ] を選びます。

  2. [プロパティ] ペインで、[デバッグ] タブを選択し、[デバッグ]>[ネイティブ コードのデバッグを有効にする] オプションを選択します。

    Visual Studio でネイティブ コード デバッグを有効にするプロパティを設定する方法を示すスクリーンショット。

    このオプションで、すべてのデバッグ セッションで、混合モードが有効になります。

    ヒント

    ネイティブ コードのデバッグを有効にすると、一時停止して [続行するには、任意のキーを押してください] プロンプトが表示されることなく、プログラムが終了するとすぐに、Python の出力ウィンドウが閉じることがあります。 ネイティブ コードのデバッグを有効にした後に強制的に一時停止とプロンプト表示を実行するには、[デバッグ] タブの [実行]>[インタープリター引数] フィールドに -i 引数を追加します。この引数によって、Python インタープリターはコードの実行後に対話モードになります。 プログラムは、ユーザーが Ctrl+Z+Enter キーを選択してウィンドウを閉じるのを待ちます。

  3. [ファイル]>[保存] (または Ctrl+S) を選択して、プロパティの変更内容を保存します。

  4. 混合モード デバッガーを既存のプロセスにアタッチするには、[デバッグ]>[プロセスにアタッチ] を選択します。 ダイアログが開きます。

    1. [プロセスにアタッチ] ダイアログで、リストから適切なプロセスを選択します。

    2. [アタッチ先] フィールドで、[選択] オプションを使用して [コードの種類の選択] ダイアログを開きます。

    3. [コードの種類の選択] ダイアログで、[次のコードの種類をデバッグする] オプションを選びます。

    4. 一覧で [Python (ネイティブ)] チェックボックスを選び、[OK] を選択します。

      Visual Studio でデバッグ用の Python (ネイティブ) コードの種類を選択する方法を示すスクリーンショット。

    5. [アタッチ] を選択してデバッガーを起動します。

    コードの種類の設定は保持されます。 混合モード デバッグを無効にし、後で別のプロセスにアタッチする場合は、[Python (ネイティブ)] のコードの種類のチェックボックスをオフにして、[ネイティブ] のコードの種類のチェックボックスを選択します。

    [ネイティブ] オプションに加えて、またはその代わりとして、他のコードの種類を選択できます。 たとえば、マネージド アプリケーションが CPython をホストし、その CPython ではネイティブ拡張モジュールが使用されているという状況で、3 つのコードのプロジェクトをすべてデバッグする場合は、[Python][ネイティブ][マネージド] のチェックボックスを選択します。 このアプローチを取ることで、呼び出し履歴の結合や 3 つのランタイム間のステップ実行など、統一されたデバッグ エクスペリエンスを得られます。

仮想環境を使用する

仮想環境 (venvs) の混合モード デバッグのこの方法を使用する場合、Windows 用の Python は、Visual Studio でサブプロセスとして検出され読み込まれる venvs 用の python.exe スタブ ファイルを使用します。

  • Python 3.8 以降の混合モードでは、マルチプロセス デバッグはサポートされていません。 デバッグ セッションを開始すると、アプリケーションではなく、スタブ サブプロセスがデバッグされます。 アタッチのシナリオでこれを回避するには、正しい python.exe ファイルにアタッチします。 デバッグ (F5 キーボード ショートカットなど) でアプリケーションを起動すると、コマンド C:\Python310-64\python.exe -m venv venv --symlinks を使用して venv を作成できます。 コマンドには、任意のバージョンの Python を挿入します。 既定では、Windows でシンボリック リンクを作成できるのは管理者のみです。

  • 3.8 以前のバージョンの Python では、混合モード デバッグは venv で想定どおりに動作します。

グローバル環境で実行すると、どのバージョンの Python でもこれらの問題は発生しません。

Python シンボルをインストールする

初めて混合モード デバッグを起動すると、[Python シンボルが必要] ダイアログが表示される場合があります。 どの Python 環境でも、シンボルをインストールする必要があるのは 1 回のみです。 Visual Studio インストーラーで Python のサポートをインストールすると、シンボルが自動的に追加されます (Visual Studio 2017 以降)。 詳細については、「Visual Studio で Python インタープリターのデバッグ シンボルをインストールする」を参照してください。

Python ソース コードにアクセスする

デバッグ時に標準的な Python 自体のソース コードを使用できるようにすることができます。

  1. https://www.python.org/downloads/source/ 」を参照してください。

  2. お使いのバージョンに適した Python ソース コード アーカイブをダウンロードし、コードをフォルダーに抽出します。

  3. Visual Studio で Python ソース コードの場所を尋ねるプロンプトが表示されたら、抽出フォルダー内の特定のファイルをポイントします。

C/C++ プロジェクトでの混合モード デバッグの有効化

Visual Studio 2017 バージョン 15.5 以降では、C/C++ プロジェクトからの混合モード デバッグがサポートされています。 この使用の例は、python.org で説明されているように、Python を別のアプリケーションに埋め込む場合です。

次に、C/C++ プロジェクトの混合モード デバッグを有効にする方法の手順を説明します。

  1. ソリューション エクスプローラーで、C/C++ プロジェクトを右クリックし、[プロパティ] を選択します。

  2. [プロパティ ページ] ペインで、[構成プロパティ]>[デバッグ] を選択します。

  3. [起動するデバッガー] オプションのドロップダウン メニューを展開し、[Python/ネイティブ デバッグ] を選択します。

    Visual Studio で C/C++ プロジェクトの Python ネイティブ デバッグ オプションを選択する方法を示すスクリーンショット。

    Note

    [Python/ネイティブ デバッグ] オプションが表示されない場合は、まず Visual Studio インストーラーを使用して Python ネイティブ開発ツールをインストールする必要があります。 ネイティブ デバッグ オプションは、Python 開発ワークロードで使用できます。 詳細については、「Visual Studio での Python サポートのインストール」をご覧ください。

  4. [OK] を選択して変更を保存します。

プログラム起動ツールをデバッグする

このメソッドを使用すると、子 py.exe サブプロセスが生成されるため、python.exe プログラム起動ツールをデバッグできません。 デバッガーはサブプロセスにアタッチされません。 このシナリオでの回避策は、次のように引数を指定して、python.exe プログラムを直接起動することです。

  1. C/C++ プロジェクトの [プロパティ ページ] ペインで、[構成プロパティ]>[デバッグ] タブに移動します。

  2. [コマンド] オプションでは、python.exe プログラム ファイルへの完全なパスを指定します。

  3. "コマンド引数" フィールドで必要な引数を指定します。

混合モード デバッガーをアタッチする

Visual Studio 2017 バージョン 15.4 以前では、直接混合モード デバッグは、Visual Studio で Python プロジェクトを起動する場合にのみ有効になります。 C/C++ プロジェクトではネイティブ デバッガーのみを使用するため、サポートは制限されています。

このシナリオの回避策は、デバッガーを個別にアタッチすることです。

  1. デバッグなしで C++ プロジェクトを開始するには、[デバッグ]>[デバッグなしで開始] を選択するか、キーボード ショートカット Ctrl+F5を使用します。

  2. 混合モード デバッガーを既存のプロセスにアタッチするには、[デバッグ]>[プロセスにアタッチ] を選択します。 ダイアログが開きます。

    1. [プロセスにアタッチ] ダイアログで、リストから適切なプロセスを選択します。

    2. [アタッチ先] フィールドで、[選択] オプションを使用して [コードの種類の選択] ダイアログを開きます。

    3. [コードの種類の選択] ダイアログで、[次のコードの種類をデバッグする] オプションを選びます。

    4. 一覧で [Python] チェックボックスを選び、[OK] を選択します。

    5. [アタッチ] を選択してデバッガーを起動します。

ヒント

デバッガーをアタッチする前にデバッグ対象の Python コードが呼び出されないように、C++ アプリケーションに一時停止または遅延を追加できます。

混合モード固有の機能を確認する

Visual Studio には、より簡単にアプリケーションをデバッグできる混合モード デバッグ機能がいくつか用意されています。

結合された呼び出し履歴を使用する

[呼び出し履歴] ウィンドウには、ネイティブと Python のスタック フレームの両方が、2 つの間の遷移情報を挟んで交互に表示されます。

Visual Studio での混合モード デバッグを使用した結合された呼び出し履歴ウィンドウのスクリーンショット。

  • 遷移の方向を指定しないで遷移を [外部コード] として表示するには、[ツール]>[オプション]>[デバッグ]>[全般]>[マイ コードのみを有効にする] オプションを設定します。

  • 呼び出しフレームをアクティブにするには、フレームをダブルクリックします。 このアクションにより、可能であれば、対応するソース コードも開きます。 ソース コードが入手できない場合でも、フレームはアクティブになり、ローカル変数を調べることができます。

Python とネイティブ コード間でのステップ実行

Visual Studio では、混合モード デバッガ―を有効にしてコードの種類間の変更を正しく処理するために、[ステップ イン] (F11 キー) コマンドまたは [ステップ アウト] (Shift+F11 キー) コマンドを使用できます。

  • C で実装された型のメソッドを Python が呼び出すときに、そのメソッドへの呼び出しにステップ インすると、メソッドを実装するネイティブ関数の先頭で停止します。

  • この動作は、ネイティブ コードが Python API 関数を呼び出し、その結果 Python コードが呼び出された場合にも発生します。 Python にもともと定義されていた関数の値で PyObject_CallObject への呼び出しにステップ インすると、Python 関数の先頭で停止します。

  • Python からネイティブへのステップ インは、Python から ctypes 経由で呼び出されるネイティブ関数でもサポートされています。

ネイティブ コード内の PyObject 値の表示を使用する

ネイティブ (C または C++) フレームがアクティブのときは、そのローカル変数がデバッガーの [ローカル] ウィンドウに表示されます。 ネイティブの Python 拡張モジュールでは、これらの変数の多くは PyObject 型 (_object の typedef) か、他のいくつの基本的な Python 型になります。 混合モード デバッグでは、これらの値は、[Python ビュー] とラベル付けされた別の子ノードを表示します。

  • 変数の Python 表現を表示するには、ノードを展開します。 変数のビューは、同じオブジェクトを参照するローカル変数が Python フレームに存在する場合に表示される内容と同じです。 このノードの子は編集可能です。

    Visual Studio の [ローカル] ウィンドウの Python ビューを示すスクリーンショット。

  • この機能を無効にするには、[ローカル] ウィンドウ内を右クリックし、[Python]>[Python ビュー ノードを表示] メニュー オプションを切り替えます。

    [ローカル] ウィンドウの [Python ビュー ノードの表示] オプションを有効にする方法を示すスクリーンショット。

Python ビュー ノードを表示する C の型

次の C の型は、有効になっている場合、[Python ビュー] ノードを表示します。

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

カスタム作成した型は、[Python ビュー] に自動的に表示されることはありません。 Python 3.x 用の拡張機能をカスタム作成する場合、この欠如は通常問題になりません。 どのオブジェクトも結局は、記述された C 型のいずれかの ob_base フィールドを持っているため、[Python ビュー] は表示されます。

Python コード内のネイティブ値を表示する

Python フレームがアクティブの場合、[ローカル] ウィンドウでネイティブ値に対して [C++ ビュー] を有効にできます。 この機能は、既定で有効になっています。

  • この機能を有効にするには、[ローカル] ウィンドウで右クリックし、[Python]>[C++ ビュー ノードを表示] メニュー オプションを設定します。

    [ローカル] ウィンドウの [C++ ビュー ノードの表示] オプションを有効にする方法を示すスクリーンショット。

  • [C++ ビュー] ノードには、値の基になる C/C++ 構造体の表現 (ネイティブ フレームで表示される内容と同じもの) が表示されます。 Python の長整数の _longobject インスタンス (その PyLongObject は typedef です) が表示され、カスタム作成されたネイティブ クラスの型の推測が試行されます。 このノードの子は編集可能です。

    Visual Studio の [ローカル] ウィンドウの C++ ビューを示すスクリーンショット。

オブジェクトの子フィールドが PyObject 型、またはサポートされている別の型の場合は、[Python ビュー] 表現ノードがあります (これらの表現が有効になっている場合)。 この動作により、リンクが Python に直接公開されていないオブジェクト グラフを移動できます。

Python オブジェクトのメタデータを使用してオブジェクトの型を特定する [Python ビュー] ノードとは異なり、[C++ ビュー] には同じように信頼性の高いメカニズムはありません。 一般的に言えば、Python 値 (つまり PyObject 参照) が与えられた場合、そのバックにある C/C++ 構造体はどれかを確実に判断することはできません。 混合モード デバッガーは、関数ポインターの型を持つ、オブジェクトの型のさまざまなフィールド (ob_type フィールドで参照される PyTypeObject など) を調べて、型を推測しようとします。 関数ポインターのいずれかが解決可能な関数を参照し、その関数に PyObject* よりも型が明確な self パラメーターがあれば、その型はバッキング型であるとみなされます。

次の例を考えてみましょう。ここで、指定オブジェクトの ob_type->tp_init 値は次の関数を指しています。

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

この場合、デバッガーは、オブジェクトの C の型は FobObject であることを正しく推測できます。 デバッガ―が tp_init から正確な型を判別できない場合は、他のフィールドに移動します。 どのフィールドからも型を推測できない場合、オブジェクトは [C++ ビュー] ノードに PyObject インスタンスとして表示されます。

カスタム作成した型の有用な表現を常に取得する最善の方法は、型を登録するときに少なくとも 1 つの特殊な関数を登録し、厳密に型指定された self パラメーターを使用することです。 ほとんどの型は、その要件を自然に満たします。 他のタイプの場合、tp_init 検査は通常、この目的に使用する最も便利なエントリです。 デバッガーの型推測を可能にするためにのみ存在する tp_init 型のダミー実装は、上記の例のように、すぐにゼロを返すことができます。

標準的な Python デバッグとの違いを確認する

混合モード デバッガーは、標準的な Python デバッガーとは異なります。 追加機能がいくつか導入されていますが、次のような Python 関連の機能が欠如しています。

  • サポートされていない機能には、条件付きブレークポイント、[Debug Interactive] ウィンドウ、プラットフォーム間のリモート デバッグなどがあります。
  • [イミディエイト] ウィンドウは使用できますが、その機能はサブセットに制限され、このセクションに記載されている制限もすべて適用されます。
  • サポートされている Python のバージョンは CPython 2.7 と 3.3+ のみです。
  • Visual Studio Shell で Python を使用する際 (統合インストーラーでインストールする場合など) に、Visual Studio で C++ プロジェクトを開くことができません。 そのため、C++ ファイルの編集は、基本的なテキスト エディターの操作のみになります。 ただし、C/C++ のデバッグと混合モードでのデバッグは Shell で完全にサポートされ、ソース コード、ネイティブ コードのステップ イン、およびデバッガー ウィンドウでの C++ 式の評価を実行できます。
  • デバッガー ツールの [ローカル] ウィンドウと [ウォッチ] ウィンドウに Python オブジェクトを表示すると、混合モード デバッガーでは、オブジェクトの構造のみが表示されます。 プロパティの自動評価や計算される属性の表示は行われません。 コレクションでは、組み込みコレクション型 (tuplelistdictset) の要素のみを表示します。 カスタム コレクション型は、組み込みコレクション型から継承される場合を除き、コレクションとして視覚化されません。
  • 式の評価は、次のセクションで説明するように処理されます。

式の評価を使用する

標準的な Python デバッガーでは、デバッグ対象のプロセスは、I/O 操作またはその他の同様のシステム呼び出しでブロックされていない限り、コード内のどのポイントで一時停止された場合でも、[ウォッチ] ウィンドウと [イミディエイト] ウィンドウで任意の Python 式を評価することができます。 混合モード デバッグの場合、Python コードで式が停止したとき、ブレークポイントの後、またはコードをステップ実行するときにのみ、任意の式を評価できます。 式は、ブレークポイントまたはステップ実行操作が発生したスレッドでのみ評価できます。

デバッガーがネイティブ コードで停止する場合、または説明されている条件が適用されない Python コードで停止する場合 (ステップアウト操作後や、別のスレッド上など)。 式の評価は、現在選択されているフレームのスコープ内のローカル変数とグローバル変数へのアクセス、フィールドへのアクセス、およびリテラルを使用した組み込みコレクション型のインデックス作成に限定されます。 たとえば、次の式は、すべてのコンテキストで評価できます (ただし、すべての識別子が既存の変数と適切な型のフィールドを参照していることを条件とします)。

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

混合モードのデバッガーも、このような式を異なる方法で解決します。 すべてのメンバー アクセス操作は、直接的にオブジェクトの一部であるフィールド (__dict__ または __slots__ 内のエントリや、tp_members 経由で Python に公開されているネイティブ構造体のフィールドなど) のみを検索し、すべての __getattr____getattribute__、または記述子ロジックを無視します。 同様に、すべてのインデックス作成操作は __getitem__ を無視して、コレクションの内部データ構造に直接アクセスします。

一貫性を保つために、この名前解決スキームは、限定式評価の制約に一致するすべての式に使用されます。 このスキームは、現在の停止ポイントで任意の式が許可されるかどうかに関係なく適用されます。 フル機能のエバリュエーターを使用できるときに Python の適切なセマンティクスを適用するには、式をかっこで囲みます。

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