Python 本体をステップ実行する方法(VSCode)

Python本体をステップ実行する方法の解説ページアイキャッチ

このページでは Python 本体をステップ実行する方法について解説していきたいと思います!

例えば、下記コマンドを実行すれば、

python test.py

Python 本体が起動し、Python 本体が引数で指定された test.py を読み込んで命令を解釈し、それが Python 本体から実行されます。

今回は、この test.py ではなく、スクリプトを実行する側の Python 本体をステップ実行する方法について解説していきます。

今回ステップ実行する対象

より具体的には、C言語 で作成された Python(つまり CPython)を VSCode 上でステップ実行する方法について解説していきます。

このステップ実行をすることで、Python がスクリプトをどのように解釈しているのかや、ガベージコレクションをどのようにして実現しているのか、などなど、Python の多くの仕組みを目で確認しながら知ることができます。

Python のソースコードは GitHub 等で公開されているので、もちろんソースコードを読むことで、どのように実装されているかは把握することはできます。ですが、実際に一行ずつ動作させながら処理を確認した方が Python の動作を理解しやすいと思います。

もちろん、ステップ実行すれば簡単に Python 本体の動作を理解できるというわけではないです。私もステップ実行して動作を確認していますが、かなりソースコードは難しいです。私レベルでは読み解くのは厳しい…。

ただ、ステップ実行できる環境が無いよりかは、ある方が絶対に動作は理解しやすいとは思います!

とにかく、プログラミング言語を作ってみたい!という方や、プログラミング言語の作りに興味があるという方には有益な情報になると思います!

環境としては MacOSX 上でのステップ実行の方法の解説になりますが、おそらく次の Python 本体をステップ実行するまでの流れ は、どの OS においても同じだと思いますし、他の OS を使用している方にもこのページの内容は参考になると思います。

MEMO

このページで記載している内容については下記の環境で確認を行なっています

  • OS:macOS Big Sur(11.5.2)
  • プロセッサ:Intel(M1 ではない)
  • Python バージョン:3.9.7

Python 本体をステップ実行するまでの流れ

まずは、ステップ実行するまでの流れと、各手順が必要な理由について説明していきます。

具体的な手順をすぐに知りたい方は、次の C言語 の環境準備 に進んでいただければと思います。

ステップ実行するまでに行うことの流れは下記になります。

まず、このページで最終的に目指すことは、下のアニメのように、VSCode で Python 本体のプログラムを一行ずつ止めながら実行(ステップ実行)できるような環境を整えることになります。

python本体をステップ実行する様子

上のアニメでは、Python の実行可能ファイルを実行した際に、最初に実行される関数(main 関数)でプログラム(Python)を停止させ、そこから一行ずつプログラムを停止させながら実行する様子を示したものになります。

例として main 関数で停止させていますが、実行される処理であれば、どの位置ででもプログラムを停止させることが可能です。

で、これを行うためには、Python 本体のソースコードを VSCode で開き、停止させたい位置にブレークポイントを設定し、さらには VSCode から Python の実行可能ファイル(python コマンドを実行した時に実行されるファイルと同等のもの)を実行する必要があります。

これらの手順については、3. の Python のステップ実行 で解説を行います。

ただし、VSCode から実行する Python の実行可能ファイルは単なる実行可能ファイルではダメです。ステップ実行を行うためには、”デバッグ情報” という情報が付加された実行可能ファイルである必要があります。

このようなデバッグ情報が付加された実行可能ファイルはおそらく通常は配布されていませんし、自身の PC 環境に応じた実行可能ファイルを用意する必要があるので、自身の PC 上でソースコードからビルドを行い(ソースコード一式から実行可能ファイルを作成する)、実行可能ファイルを作成する必要があります。

この手順については、2. の Python のビルド で解説します。

さらに、今回は CPythonC言語 で作成された Python)のステップ実行を行いますので、ソースコードは C言語 で記述されたものになります。

ですので、C言語 のソースコードをコンパイルする環境が必要になります。

さらには、C言語 プログラムをステップ実行することになるので、C言語 プログラムをステップ実行できる環境を整えておく必要があります。

これらの手順については、1. の C言語 の環境準備 で解説を行います。が、ここは使用している PC の OS によって手順が異なりますので、主に MacOSX 向けの解説になります。他の OS を使用している方は、別途その OS 向けのページを参考にしながら環境を整えていただければと思います(すでにバリバリ C言語 プログラムをデバッグしているような方は、この章で行う準備は不要かもしれません)。

大体このページでやる作業や各作業が必要になる理由を理解していただけたでしょうか?

ここからは、それらの作業を行う上での具体的な手順を解説していきたいと思います。

C言語 の環境準備

まずは準備として、C言語 ソースコードのコンパイルや make を行える環境、さらには C言語 プログラムをステップ実行する環境を整える必要があります。

もしこれらの環境が整っている場合は、次の Python のビルド までスキップしていただければと思います。

スポンサーリンク

C言語 をコンパイルできる環境を整える

前述の通り、今回は CPythonC言語 で作成された Python)のステップ実行を行う方法の解説になります。

その際に、C言語 で記述されたソースコードをコンパイルする必要があるので、C言語 のソースコードをコンパイルする環境をまず整える必要があります。おそらく gcc がインストールされていれば良いと思います。

また、コンパイルは、make コマンドから実行される構成になっていますので、make コマンドも実行できる必要があります。Pythonのプログラムのソースコードは大量のファイルから構成されますが、この make コマンドを実行することで、それらのファイルを一括してコンパイルすることが可能です。

つまり、最低限、下記の2つはインストールしておく必要があります。

  • gcc
  • make

MacOSX の場合は、Xcode コマンドラインツールがインストールされていれば、おそらく上記の2つもインストールされていると思います。

まだインストールされていない方は、まずこの Xcode コマンドラインツールをインストールしておきましょう!

このインストール方法はググれば解説ページにすぐ辿り着けますし、ターミナルアプリを使って下記のコマンドでインストールすることも可能だと思います(コマンド実行するとダイアログが立ち上がり、ボタン操作でインストールすることが可能です)。

xcode-select --install

VSCode で C言語 プログラムをステップ実行できる環境を整える

また、今回は VSCode 上で C言語で記述された Python をステップ実行していくので、まずは VSCode 上で C言語 プログラムが実行できる環境を整えておく必要があります。

MacOSX に関しては下記ページで環境の整え方を解説していますので、よろしければ参考にしていただければと思います。

VSCodeでMacOSにC言語デバッグ環境を構築する方法の解説ページアイキャッチVSCodeでMacOSにC言語デバッグ環境を構築

ちなみに、上記ページでは lldb を利用していますが、gdb でも良いと思います。また、デバッグに使用するプラグインとして、CodeLLDB を使用していますが、C/C++ プラグイン を使用しても良いです。

要はステップ実行ができる環境さえ整えればオーケーです。

Python のビルド

続いて、Python のソースコードをビルドして、Python の実行可能ファイルを作成していきます。

実はこのやり方は、以前に私のサイトの下記ページで紹介しています。私も書いたの忘れました…。

Python のビルド方法解説ページのアイキャッチPython をソースコードからビルドする

上記ページでは、ソースコード入手の方法の詳細やオプション指定などの例も紹介しているので、ビルドの方法の詳細に関しては上記ページを参照していただければと思います。

ここでは簡潔に、Python のステップ実行に必要なことだけ解説していきたいと思います。

スポンサーリンク

Python のソースコードを入手する

まずは、Python のソースコードを入手します。

Python のソースコードは、下記 URL からダウンロードすることができます。

https://github.com/python/cpython

上記 URL にアクセスすると、下の図のような画面が表示されます。

cpythonレポジトリを表示した画面

ここから好きなバージョンのソースコードを取得すれば良いのですが、できれば安定版、リリース版のソースコードを入手する方が良いと思います(最新版だと安定していない可能性もあるので)。

ということで、まずはタグ付けされたバージョンの表示を行います。”xxx tags” と記載されているテキストをクリックすれば、タグ付けされたバージョンを表示することができます。下の図では “484 tags” と記載されているテキストですね!おそらくアクセスしたタイミングによって数字は異なると思います。

タグづけされたバージョンを表示するために押す場所の説明図

ボタンを押すと下の図のような画面に遷移しますので、ここから自身がステップ実行したい Python のバージョンのソースコードをダウンロードします。

タグづけされたバージョンの表示結果

今回私は、バージョン 3.9.7 のソースコードを ZIP 形式でダウンロードしました。具体的には、下の図のオレンジ枠で囲った部分をクリックしてダウンロードを行っています。

ソースコードダウンロード時にクリックする位置の説明図

tar.gz 形式でダウンロードすることもできるので、都合の良い方の形式を選べば良いです。

で、このダウンロードした ZIP ファイルを展開すれば、その中に Python のソースコード一式が入っています。

Pythonソースコード一式

あとは、この展開したフォルダをそのまま、好きなフォルダに移動してやれば、ソースコードの入手は完了です!移動先はどこでも良いと思います。今回はインストールまではやらないので、おそらくフォルダ外への影響はないと思います。

今後は、上の図のファイル群が設置されているフォルダを “ワークフォルダ” として解説していきたいと思います。

Python のソースコードをビルドする

続いて Python のビルドを行っていきます。

コマンド実行できる環境の立ち上げ

まずはコンソールからコマンド実行できる環境を立ち上げます。Windows だとコマンドプロンプトかな…?Power Shell…?

とりあえず MacOSX の場合は、ターミナルアプリからコマンド実行できますので、ターミナルアプリを起動します。

ターミナルアプリは下記のようにフォルダを辿れば見つかると思います。

アプリケーション → ユーティリティ

ユーティリティフォルダには下の図のようにさまざまなアプリが配置されていますが、名前が “ターミナル” になっているのが、今回使用するターミナルアプリを起動するためのアイコンになります。

ターミナルアプリが配置されているユーティリティフォルダの中身

アイコンをダブルクリックすれば、ターミナルアプリが起動します。

ターミナルアプリ起動画面

ワークフォルダへの移動

続いて、ターミナルアプリでコマンド実行を行なって、ワークフォルダ(Python のソースコード一式を設置したフォルダ)に移動します。

このフォルダの移動は、下記のように cd コマンドを実行することで行うことができます(最初の % は入力不要です。今後も % は何回も出てきますが、全て同様に入力不要です。また、% ではなく $ が表示されている場合もあると思います)。

% cd 移動先のフォルダパス

例えば、私は Python のソースコード一式を /Users/daeu/Desktop/Python_debug というフォルダに設置しましたので、下記コマンドを実行して移動を行いました。

% cd /Users/daeu/Desktop/Python_debug

移動後に、pwd と入力してエンターキーを押し、ワークフォルダのフォルダパスが表示されれば成功です(pwd は現在いるフォルダのパスを表示するコマンドです)。

% pwd
/Users/daeu/Desktop/Python_debug

以降では、このフォルダで configuremake を実行し、Python.exe を作成していきます。

configure の実行

続いて、configure の実行を行います。configure はあなたの PC 環境に合わせてコンパイルを行うための Makefilepyconfig.h 等を作成するスクリプトになります。

下記コマンドにより、この configure を実行することができます。

% ./configure --with-pydebug

ここで重要なのは、上記のように --with-pydebug オプションを指定することです。これにより、ステップ実行可能な Python 実行可能ファイルを作成するための設定が自動的に行われるようになります(最適化が OFF になる)。

MEMO

ただし、--with-pydebug オプションを指定すると、configure 実行後に作成される pyconfig.h に下記の define が加わることになります

pyconfig.h
#define Py_DEBUG 1

上記の定義が加わることによって、通常の方法でインストールした時の Python と若干動きが異なるので注意してください(デバッグしやすいようにするための機能が加わったりする)

おそらく Python の動きを追う上では特に気にならない程度の差異だとは思います

もし、よりリリース版の Python に動きを合わせた状態でステップ実行したい場合は、下記のようにコメントアウトしてから、次に説明する make を実行すれば良いはずです

pyconfig.h
/* #define Py_DEBUG 1 */

が、私が実際に試すと make 時に大量のログが出力されるようになり、そのせい?でコマンドが最後まで終了しませんでした…

大量にログが出始めた時点では python.exe はすでに出来上がっているので、Ctrl + C で強制終了すれば一応ステップ実行は可能ですが…(それでも python.exe を実行すると大量のログが出力されてしまう…)

pyconfig.h は手動では直さないほうが無難かもしれないです…

configure に成功すると、最後に下記のようなメッセージが表示されます。

creating Makefile

これが表示されれば configure の実行は完了です。ワークフォルダ内に Makefile というファイルが作成されているはずです。

Makefileが作成される様子

configure では、次に実行する make コマンドで必要になるライブラリなどが足りているかどうかのチェックも行われます。もし足りない場合は、configure 実行時にエラーが発生しますので、別途それらのライブラリをインストールする必要があるので注意してください。

下記のページでは必要なライブラリを補いながら configure を進めた時の過程が解説されていますので、参考になるかもしれないです。

https://qiita.com/kenta_ojapi/items/6b19e0c05b268f3e74da

また、もし MacOSX で configure 時に下記のようなエラーが出る場合、Xcode コマンドラインツールを入れ直せば、エラーが出なくなるようになるかもしれません。

checking size of long double... configure: error: in `/Users/daeu/Documents/cpython/cpython':
configure: error: cannot compute sizeof (long double)
See `config.log' for more details
configure: error: check config.log and use the '--with-universal-archs' option

どうもこのエラーが出るのは Xcode コマンドラインツールが古い?からのようで、下記の2つのコマンドで再インストールしたらエラーが出なくなりました(1行目で Xcode コマンドラインツールの削除、2行目で Xcode コマンドラインツールのインストール)。

% sudo rm -r /Library/Developer/CommandLineTools
% xcode-select --install

同様の現象が発生した方や、他のエラーが発生した場合でも、上記を試すとうまくいくようになるかもしれないです(ちなみに、再インストールではなく、単にアップデートしただけだとエラーは解消されませんでした)。

make の実行

続いて、make コマンドを実行していきます。この make コマンドを実行することで、先ほど configure で作成された Makefile に従って、Python ソースコード一式がコンパイルされ、最後にリンクが行われて python.exe が作成されます。この python.exe が、Python 本体の実行可能ファイルになります。

make の実行するためには、下記コマンドを実行すれば良いだけです。

% make

実行すれば、どんどんコンパイルが行われていき、エラーが出ずにコマンド終了すれば make の完了です。

make が完了すると、ワークフォルダ内に python.exe というファイルが作成されていることが確認できると思います。

python.exeが作成される様子

python.exe の動作確認

次は、make コマンドで作成された python.exe を実行してみましょう!下記コマンドにより、python.exe が実行できます。

% ./python.exe

うまく make が出来ていれば、下記のように Python を単体起動することができるはずです。

表示される文言は、実行する環境やダウンロードしてきた Python のバージョン等が異なるので注意してください。私は今回 Python のバージョン 3.9.7 をダウンロードしてきたので、Python 3.9.7 と表示されていますね。

% ./python.exe 
Python 3.9.7 (default, Sep  8 2021, 08:15:14) 
[Clang 12.0.5 (clang-1205.0.22.11)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

起動できることが確認できたら、quit() と入力してからエンターキーを押して、python.exe を終了させましょう。

次は Python スクリプトを実際に実行してみましょう!

まずは何かしらの簡単な Python スクリプトを作成し、test.py という名前でワークフォルダに保存します。要は、先ほど実行した python.exe があるフォルダに、test.py を保存します。

MEMO

import する場合、いつも使用しているモジュールであっても、そのモジュールがインストールされていない or make されていない可能性があるので注意してください

実行するスクリプトがなんでも良いのであれば、例えば下記のスクリプトをコピペして test.py という名前で保存するので良いです。

test.py
# -*- coding: utf-8 -*-
import random

class TestClass:
    def __init__(self, num):
        print("Constructor")
        self.num = num

    def print(self):
        print("num = " + str(self.num))

    def __del__(self):
        print("Destructor")

# int型がうまく扱えるか確認
a = 5

# print関数がうまく動作するか確認
print(a)

# id関数がうまく動作するか確認
print(id(a))

# クラスがうまく扱えるか確認
a = TestClass(1234)
a.print()
a = None

# importしたモジュールが動作するか確認
print(random.random())

# 以降はリストがうまく扱えるかの確認
b = [1, 2, 3, 4, 5]

for i in range(3):
    b.append(i + 100)

print(b.pop())
print(b.pop())

for c in b:
    print(c)

続いて、make コマンドで作成した python.exetest.py を実行させます。これは、下記のコマンドで実行することができます。

./python.exe test.py

意図した通りに Python スクリプトが実行されているようであれば、python.exe がうまく実行されたと考えて良いと思います!

もし上記のスクリプトをコピペしたのであれば、下記のように結果が表示されるはずです(id() で表示される値と random.random() で表示される値は、実行するたびに変化するので同じ値にはならないので注意してください)。

5
4387234752
Constructor
num = 1234
Destructor
0.9067487974828838
102
101
1
2
3
4
5
100

Python のステップ実行

それでは、いよいよ Python をステップ実行していきます!

スポンサーリンク

VSCode からワークフォルダを開く

まずは VSCode から、先ほど作業していたワークフォルダを開きます。

まず VSCode を開き、メニューから「ファイル → 開く」を選択します(これは VSCode を日本語化している場合になります。英語の場合は 「FILE → Open」になりますかね…。ちょっとはっきりは分からないですが…)。

そうすると、フォルダを選択するダイアログが開きますので、そこで、先ほどまで作業をしていたフォルダをクリックして選択し、開くボタンを押します(その選択するフォルダの直下には python.exe があるはずです)。

VSCodeからワークフォルダを選択する様子

これにより、VSCode でワークフォルダが開かれるはずです。画面左側のファイルエクスプローラーには下の図のようなファイルやフォルダが表示されるはずです。

VSCodeでワークフォルダを開いた様子

launch.json を編集する

まずは VSCode に、C言語 のデバッグを行おうとしていることを伝えるために、C言語 のソースコードファイルを表示します。

これは、画面左側のファイルエクスプローラーからてきとうな .c ファイルをクリックして表示すれば良いのですが、今後の説明に都合が良いので下記のファイルを開いていただければと思います。

Programs/python.c

python.c をファイルエクスプローラーからクリックすれば、下の図のように画面の右側に python.c の中身が表示されるはずです。

python.cを開いた様子

この状態で、続いてメニューから「実行 → デバッグ実行」を選択します。

そうすると、下の図のように、VSCode のインストール済みの C言語 デバッグ関連プラグインの一覧が表示されますので、使用したいプラグインをクリックして選択します。

おそらく、C/C++ プラグイン は “C++ (GDB/LLDB)” で、CodeLLDB プラグインは “LLDB” で表示されているはずです。もしプラグインを1つしかインストールしていない場合、この画面は表示されないかもしれないです。

プラグイン一覧が表示される様子

MEMO

C/C++ プラグインの場合は、続いてコンパイラの選択画面が表示されるかもしれないです

この場合は適当なコンパイラを選択してください(これ以降はコンパイルしないので、多分どれ選んでも問題ないと思います)

選択すると、おそらく警告画面が表示されると思います。例えば CodeLLDBの場合は下の図のような警告が表示されます。が、とりあえずこれは気にせず OK ボタンを押してください。

launch.jsonの編集なしにデバッグ開始した時に表示される警告メッセージ

ボタンを押すと、自動的に launch.json が VSCode 上に表示されるはずです(上記の操作によって launch.json が自動的に作成され、それが表示されます)。

MEMO

C/C++ プラグインの場合は上の図とは異なる警告メッセージが表示されると思います

おそらく中止ボタンが表示されるはずなので、中止ボタンをクリックしてください(とりあえずデバッグ開始をやめれば良い)

そうすると、上記同様に launch.json が自動的に作成され、launch.json が VSCode に表示されるはずです

続いて、この launch.json を編集していきます。ここで編集する目的は下記の3つ(CodeLLDB の場合は2つ)になります。

  1. 実行するプログラムファイルを python.exe に設定する
  2. 引数に test.py を設定する
  3. preLaunchTask を無しにする(C/C++ プラグインの場合)

これらは、下記のように launch.json を編集することで実現することができます。

  1. "program" の設定値を "${workspaceFolder}/python.exe" に設定する
  2. "args" の設定値を ["test.py"] に設定する
  3. "preLaunchTask" の行をコメントアウトする

少し補足しておくと、${workspaceFolder} 部分は VSCode からワークフォルダを開く で開いたフォルダのパスに自動的に置き換えが行われます。

"program" に設定するのは、デバッグ開始時に実行するプログラムのファイルパスになりますので、1. のように編集することで、VSCode からワークフォルダを開く で開いたフォルダの直下にある python.exe が実行されるようになります。

MEMO

もしかしたら、Windows の場合は "${workspaceFolder}/python.exe" ではなく "${workspaceFolder}¥python.exe" と記載する必要がある “かも” しれませんので注意してください

また、"args" に指定したパラメータがデバッグ開始時にプログラムに引数として渡されますので、2. により python.exe に実行したい Python スクリプト(test.py)を指定することになります。

実行したいスクリプトのファイル名に応じて、ここは適宜変更していただければと思います。

ただしこのページでは、python.exe で実行するスクリプトのファイル名は test.py として解説していきますので、その点はご注意ください。

さらに、preLaunchTask は、プログラム実行前にコンパイルを自動的に行う場合に便利ですが、今回はコンパイルは既に make コマンドにより完了しているため、不要になります。

上記のように編集した場合、CodeLLDB プラグインの場合は launch.json が下記のようになるはずです(下記をコピペしてもおそらくうまく動作すると思います)。

launch.json(CodeLLDB)
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "program": "${workspaceFolder}/python.exe",
            "args": ["test.py"],
            "cwd": "${workspaceFolder}",
        }
    ]
}

また、C/C++ プラグインの場合は launch.json が下記のようになるはずです。preLaunchTask の行はコメントアウトしていますが、もちろん削除してしまっても良いです。

こちらもコピペしても問題ないかもしれないですが、"MIMode" の設定は、デバッガーに GDB を利用する場合は "gdb" に変更する必要があると思います(あと "cwd" の設定がちょっと不安…)。

launch.json(C/C++)
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "gcc - アクティブ ファイルのビルドとデバッグ",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/python.exe",
            "args": ["test.py"],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "lldb",
            //"preLaunchTask": "C/C++: gcc アクティブなファイルのビルド"
        }
    ]
}

launch.json を作成したら、再度デバッグを開始してみましょう!先ほどと同様にメニューから「実行 → デバッグ開始」を選択します。

すると、今度は警告は表示されなくなるはずです。

さらに、Python スクリプト(test.py)を実行した結果が、画面下側の “出力” タブ or “ターミナル” タブ or “デバッグコンソール” タブのいずれかに表示されているはずです。

スクリプト実行結果が表示される様子

もし表示されていれば、VSCode から python.exe を実行する環境が整ったことになります!

Python のステップ実行! 

ということで、いよいよ Python のステップ実行を行なっていきます。

まずは、ソースコードにブレークポイントを設定しましょう!

あなた自身がステップ実行したい位置にブレークポイントを設定するので良いのですが、ここではまず、python.exe を実行すれば必ず通過する位置にブレークポイントを設定し、実際にそこでプログラムが停止するかどうかを確認してみましょう!

必ず通る場所の一例が、先ほどの launch.json を編集する で開いた python.cmain 関数になります(Windows の場合は多分 wmain)。ここが python.exe 実行時に最初に実行される関数になります。

ということで、main 関数(Windows の場合は wmain)の中にブレークポイントを貼ってみましょう!

といっても、main 関数の中には1行しかないと思いますので、その行にブレークポイントを設定することになります。その行の、”行番号の左側” をクリックすれば、クリックした位置に赤丸が表示されると思います。

ブレークポイントを設定する様子

この赤丸が表示されればブレークポイントの設定は完了です!

うまくデバッグ情報を付加した状態で python.exe が作成されている場合&そのデバッグ情報を付加した際のソースコードを開いている場合、デバッグを開始すれば、そのブレークポイントでプログラムが停止することになります。

ということで、再度デバッグを実行してみましょう!メニューの「実行 → デバッグ開始」からデバッグは実行できますね!(デバッグウィンドウを表示して再生ボタンを押すのでも良いです)。

すると、今度は test.py が実行される前に、ブレークポイントを設定した行でプログラムが停止するはずです!停止した際には、停止した行が、下の図のように黄色背景になります(色は設定等により異なるかも)。

プログラムがブレークポイントで停止する様子

停止したのであれば…、おめでとうございます!これで Python 本体をステップ実行する環境は整いました!

あとは、あなたが確認したい位置にブレークポイントを設定したり、下の図のデバッグの操作パネルを操作することで、好きな位置のソースコードをステップ実行してやればいいだけです。

デバッグ操作パネル

例えば、上の図の状態から、デバッグの操作パネルからステップイン(下矢印のボタン)をクリックすれば、停止した位置から実行しようとしている関数(上の図の場合は Py_BytesMain 関数)の中に入って一行ずつプログラムを停止しながら、この関数の中の動作を調べることができます。

Py_ByteMainに入り込んでステップ実行を進める様子

また、画面左側のウィンドウから、変数の中身やコールスタック(関数がどのように呼ばれて現在の処理の位置まで到達したかの履歴)を確認することも可能です。

デバッグのやり方の詳細は下記のページで紹介していますので、これらを駆使していろんなステップ実行を行なってみてください!

VSCodeでMacOSにC言語デバッグ環境を構築する方法の解説ページアイキャッチVSCodeでMacOSにC言語デバッグ環境を構築

もし、どこにブレークポイントを設定してもプログラムが停止しないという場合、下記を確認してみてください。

  • configure 実行時に --with-pydebug オプションを指定したかどうか
    • おそらくこのオプションを付けないと最適化が効いてしまって、プログラムが停止しなくなると思います
  • make 実行後にソースコードの位置(フォルダ)を変更していないか
    • コンパイル時に付加されるデバッグ情報にはソースコードへのパスが記述されているはずです
    • なので、ソースコードの位置を変更してしまうとデバッグ情報とソースコードのパストで話が合わなくなって、ブレークポイントがうまく設定できなくなります

スポンサーリンク

いろんな位置にブレークポイントを設定してみる

といっても、ソースコードの規模も大きいのでどこにブレークポイントを貼れば良いかを判断するのが難しいですよね…。

ということで、どのあたりにブレークポイントを設定すれば良いかについて、いくつかの例を挙げて紹介していきたいと思います。

ただ、私もまだ解析しきれていないので、文字列検索等を駆使しながら、是非皆さんもブレークポイントをいろんなところに設定して動作確認をしてみてください。

MEMO

ここに記載している内容は私がステップ実行しながら解析した内容になりますので、間違っている可能性もあるので注意してください

また、Python のバージョンや取得したソースコードのリビジョンが異なる場合、ソースコードの構成も異なる可能性があるので注意してください(私は 3.9.7 を使用)

組み込み関数

組み込み関数(例えば print 関数や input 関数)は、下記のファイルで定義されているようです。

Python/bltinmodule.c

例えば、Python スクリプトで print 関数が実行された際には、上記ファイルの builtin_print 関数が呼ばれます。ですので、builtin_print の先頭付近にでもブレークポイントを設定しておけば、print 関数実行時の動作をステップ実行しながら確認することができると思います。

また、input 関数の場合は builtin_input_impl 関数が呼ばれるはずです。

その他の組み込み関数の多くも、上記のファイルで builtin_xxxbuiltin_xxx_impl などの関数名で定義されていますので、この辺りがブレークポイントを設定する位置の目安になるかと思います(例えば maxminpow などの関数の実装もこのファイル内に存在します)。

標準モジュールの関数

標準モジュールの関数は Modules フォルダ内のファイルで定義されているようです。

例えば、random モジュールの関数は下記ファイルで定義されているようです。

Modules/_randommodule.c

Python スクリプトから random 関数(random.random)を実行すれば、_randommodule.c_random_Random_random_impl 関数が呼ばれることが確認できました。なので、この関数にブレークポイントを設定し、そこからステップ実行していけば、どのようにランダム値が生成されているかがわかると思います。

csv モジュールの関数は下記ファイルで定義されているようです。

Modules/_csv.c

Python スクリプトから reader 関数(csv.reader)を実行すれば、_csv.ccsv_reader 関数が呼ばれるはずです。

math モジュールについても調べてみたのですが、例えば下記のファイルに cmath_sin_impl 関数があるので、sin 関数を実行すればコレが呼ばれるのかなぁと思ったのですが、この関数は呼ばれませんでした…(よく考えたらこのファイルは複素数用の関数なので呼ばれるわけなかったですね…)。

Modules/cmathmodule.c

ステップ実行で追ってみた感じだと他のライブラリ(libm ?)で定義されている sin 関数が呼ばれてるようにも見えました。もしかしたら configure 実行時のオプション等で上記ファイルの関数が呼ばれるようになるかもしれません…。

組み込み型

組み込み型における関数の定義は Modules フォルダ以下のファイルで行われているようです。

例えば int 型であれば、下記のファイルでメソッド等が定義されています。

Objects/longobject.c

例えば bit_length メソッドであれば、上記ファイルの int_bit_length_impl 関数で実装されているようです。ですので、この関数にブレークポイントを設定しとけば、int 型のオブジェクトから bit_length メソッドを実行すると、この関数でプログラムを停止することができるはずです。

また、+ 演算子実行時には、上記ファイルの long_add 関数が呼ばれるようです。この関数にブレークポイントを設定しておき、例えば 9876 + 54321 という処理を Python スクリプトに記述してデバッグ開始すれば、いずれ下の図のように long_add でプログラムが停止するはずです。

long_add関数でプログラムが停止する様子

で、この時に、上の図のようにデバッグウィンドウの変数欄から、 ob_digit に足し算を行なっている値 987654321 が格納されていることが確認できると思います(画面小さいと見えないかも…)。

ただ、この long_add 関数は、Python スクリプト実行前の Python の初期化処理?でも大量に呼ばれてしまうので、ブレークポイントを有効にするタイミングは要検討だと思います。

私は Python の初期化処理が終わった後に、ブレークポイントを有効にして上の図のようにプログラムが停止することを確認しました。より具体的にいうと、Python/pythonrun.cpyrun_file 関数にあらかじめブレークポイントを設定しておき、ここでプログラムが停止した後に(この時点では Python の初期化は完了している)、long_add 関数に設定したブレークポイントを有効にするようにしています。

MEMO

ただし、pyrun_file 関数はブレークポイントを設定する位置の目安であり、場合によってはここが実行されないケースもあるかもしれないので注意してください

設定済みのブレークポイントの有効 or 無効は、デバッグウィンドウのブレークポイント欄から切り替えることができます(チェックを外すと無効になる)。

ブレークポイントの有効無効の設定の様子

こんな感じで、大量に呼ばれる関数にブレークポイントを設定する場合は、ブレークポイントを有効にするタイミングも重要になります。

で、話は戻って、上の long_add 関数の図を見ると PyLong_FromLong 関数が呼ばれていることが分かると思います。この関数も longobject.c で定義されており、おそらくこの関数が int 型のオブジェクトを生成している関数だと思います。

より具体的にいうと、この関数の中から _PyLong_New が呼ばれ、そこを辿っていくと、メモリの確保が行われてオブジェクトが生成されていることが分かると思います(小さい値の場合は get_small_int からすでに生成済みのオブジェクトが取得されている?)。

さらに、そのオブジェクトの ob_digit に、そのオブジェクトに対応する整数値(今回の場合は 9876 + 54321 の結果)が格納されている動作が確認できます(絶対値が格納されている?)。

例えば、a = 123456 などと Python に記述しておいても、この PyLong_FromLong が呼ばれることは確認できると思いますが、この関数も大量に呼ばれる関数なので、ブレークポイントを有効にするタイミングには注意が必要です。

ちなみに int 型の定義は下記に記述されているようです。

include/intrepr.h

おそらくですが、Python における int 型は、Python 本体では下記の構造体として扱われていると思います。

PyLongObject
struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

ガベージコレクション

ガベージコレクションは、下記ファイルの _Py_DECREF 関数で行われているようです。

include/object.h

この関数の中で、オブジェクトの参照回数を 1 減らし、0 になった場合は _Py_Dealloc が呼ばれて、そこからメモリの解放が行われるようです。

例えば自身で定義したクラスのオブジェクトへの参照がなくなった際には、_Py_Dealloc が呼ばれ、下の図のように、op.obtype.tpname に、そのクラス名(下の図の場合は DaeuClass)が格納されたオブジェクトが解放される様子が確認できるのではないかと思います。

_Py_DECREFでプログラムが停止する様子

ただメモリの解放では、free 関数が実行されるケースもあれば、メモリプールに使用していたメモリを返却することで解放するケースもあるようでした。

また、当然この _Py_DECREF も大量に呼ばれる関数なので、ブレークポイントを有効にするタイミングには注意してください。

スクリプトの実行

ステップ実行で動作を追った感じだと、おそらく事前にスクリプトを解釈した結果に基づき、下記ファイルの _PyEval_EvalFrameDefault 関数の中でループを回しながら、スクリプトに記述された処理を実行しているように見えました(mainloop: ラベル付近の無限ループ内で)。

Python/ceval.c

じゃあスクリプトの解釈はどこで行なっているのかというと、すみません、良く分かってないです。

おそらく、スクリプトを下記フォルダのファイルの関数群でパースして、

Parser

その結果を下記ファイルで中間言語にコンパイルして、

Python/compile.c

で、そのコンパイルした結果に基づいて _PyEval_EvalFrameDefault 関数から Python スクリプトに記述された処理を実行しているのではないかなぁと思います。

ちょっとはっきり分かっていないので申し訳ないのですが、Python の動作を解析する時の参考になれば幸いです。

まとめ

このページでは、Python 本体をステップ実行するための方法について解説しました。

ステップ実行することで、Python 本体の動きをより解析しやすくなると思います!特にプログラミング言語がどのように実装されているかを知りたい方にはオススメの内容になっています。

ステップ実行するまでの流れは、大きく分けると下記の3つになります。

特に2つ目の Python のビルド 時に、デバッグ情報を付加させる&最適化を OFF にするというところがポイントになると思います。

で、上記の流れを踏襲すれば、C言語 で実装されているのであれば、他のプログラミング言語も同様にステップ実行できる環境を整えることができると思います。

また、プログラミング言語だけでなく、ライブラリ等も同様にステップ実行できるようになるので、ぜひこのステップ実行するまでの流れは覚えておいてください!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です