このページでは 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 を使用している方にもこのページの内容は参考になると思います。
このページで記載している内容については下記の環境で確認を行なっています
- OS:macOS Big Sur(11.5.2)
- プロセッサ:Intel(M1 ではない)
- Python バージョン:3.9.7
Contents
Python
本体をステップ実行するまでの流れ
まずは、ステップ実行するまでの流れと、各手順が必要な理由について説明していきます。
具体的な手順をすぐに知りたい方は、次の C言語 の環境準備 に進んでいただければと思います。
ステップ実行するまでに行うことの流れは下記になります。
まず、このページで最終的に目指すことは、下のアニメのように、VSCode で Python
本体のプログラムを一行ずつ止めながら実行(ステップ実行)できるような環境を整えることになります。
上のアニメでは、Python
の実行可能ファイルを実行した際に、最初に実行される関数(main
関数)でプログラム(Python
)を停止させ、そこから一行ずつプログラムを停止させながら実行する様子を示したものになります。
例として main
関数で停止させていますが、実行される処理であれば、どの位置ででもプログラムを停止させることが可能です。
で、これを行うためには、Python
本体のソースコードを VSCode で開き、停止させたい位置にブレークポイントを設定し、さらには VSCode から Python
の実行可能ファイル(python
コマンドを実行した時に実行されるファイルと同等のもの)を実行する必要があります。
これらの手順については、3. の Python のステップ実行 で解説を行います。
ただし、VSCode から実行する Python
の実行可能ファイルは単なる実行可能ファイルではダメです。ステップ実行を行うためには、”デバッグ情報” という情報が付加された実行可能ファイルである必要があります。
このようなデバッグ情報が付加された実行可能ファイルはおそらく通常は配布されていませんし、自身の PC 環境に応じた実行可能ファイルを用意する必要があるので、自身の PC 上でソースコードからビルドを行い(ソースコード一式から実行可能ファイルを作成する)、実行可能ファイルを作成する必要があります。
この手順については、2. の Python のビルド で解説します。
さらに、今回は CPython
(C言語
で作成された Python
)のステップ実行を行いますので、ソースコードは C言語
で記述されたものになります。
ですので、C言語
のソースコードをコンパイルする環境が必要になります。
さらには、C言語
プログラムをステップ実行することになるので、C言語
プログラムをステップ実行できる環境を整えておく必要があります。
これらの手順については、1. の C言語 の環境準備 で解説を行います。が、ここは使用している PC の OS によって手順が異なりますので、主に MacOSX 向けの解説になります。他の OS を使用している方は、別途その OS 向けのページを参考にしながら環境を整えていただければと思います(すでにバリバリ C言語
プログラムをデバッグしているような方は、この章で行う準備は不要かもしれません)。
大体このページでやる作業や各作業が必要になる理由を理解していただけたでしょうか?
ここからは、それらの作業を行う上での具体的な手順を解説していきたいと思います。
C言語 の環境準備
まずは準備として、C言語
ソースコードのコンパイルや make
を行える環境、さらには C言語
プログラムをステップ実行する環境を整える必要があります。
もしこれらの環境が整っている場合は、次の Python のビルド までスキップしていただければと思います。
スポンサーリンク
C言語
をコンパイルできる環境を整える
前述の通り、今回は CPython
(C言語
で作成された 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言語デバッグ環境を構築ちなみに、上記ページでは lldb
を利用していますが、gdb
でも良いと思います。また、デバッグに使用するプラグインとして、CodeLLDB
を使用していますが、C/C++ プラグイン
を使用しても良いです。
要はステップ実行ができる環境さえ整えればオーケーです。
Python
のビルド
続いて、Python
のソースコードをビルドして、Python
の実行可能ファイルを作成していきます。
実はこのやり方は、以前に私のサイトの下記ページで紹介しています。私も書いたの忘れました…。
Python をソースコードからビルドする上記ページでは、ソースコード入手の方法の詳細やオプション指定などの例も紹介しているので、ビルドの方法の詳細に関しては上記ページを参照していただければと思います。
ここでは簡潔に、Python
のステップ実行に必要なことだけ解説していきたいと思います。
スポンサーリンク
Python
のソースコードを入手する
まずは、Python
のソースコードを入手します。
Python
のソースコードは、下記 URL からダウンロードすることができます。
https://github.com/python/cpython
上記 URL にアクセスすると、下の図のような画面が表示されます。
ここから好きなバージョンのソースコードを取得すれば良いのですが、できれば安定版、リリース版のソースコードを入手する方が良いと思います(最新版だと安定していない可能性もあるので)。
ということで、まずはタグ付けされたバージョンの表示を行います。”xxx tags” と記載されているテキストをクリックすれば、タグ付けされたバージョンを表示することができます。下の図では “484 tags” と記載されているテキストですね!おそらくアクセスしたタイミングによって数字は異なると思います。
ボタンを押すと下の図のような画面に遷移しますので、ここから自身がステップ実行したい Python
のバージョンのソースコードをダウンロードします。
今回私は、バージョン 3.9.7 のソースコードを ZIP 形式でダウンロードしました。具体的には、下の図のオレンジ枠で囲った部分をクリックしてダウンロードを行っています。
tar.gz 形式でダウンロードすることもできるので、都合の良い方の形式を選べば良いです。
で、このダウンロードした ZIP ファイルを展開すれば、その中に 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
以降では、このフォルダで configure
と make
を実行し、Python.exe
を作成していきます。
configure
の実行
続いて、configure
の実行を行います。configure
はあなたの PC 環境に合わせてコンパイルを行うための Makefile
や pyconfig.h
等を作成するスクリプトになります。
下記コマンドにより、この configure
を実行することができます。
% ./configure --with-pydebug
ここで重要なのは、上記のように --with-pydebug
オプションを指定することです。これにより、ステップ実行可能な Python
実行可能ファイルを作成するための設定が自動的に行われるようになります(最適化が OFF になる)。
ただし、--with-pydebug
オプションを指定すると、configure
実行後に作成される pyconfig.h
に下記の define
が加わることになります
#define Py_DEBUG 1
上記の定義が加わることによって、通常の方法でインストールした時の Python
と若干動きが異なるので注意してください(デバッグしやすいようにするための機能が加わったりする)
おそらく Python
の動きを追う上では特に気にならない程度の差異だとは思います
もし、よりリリース版の Python
に動きを合わせた状態でステップ実行したい場合は、下記のようにコメントアウトしてから、次に説明する make
を実行すれば良いはずです
/* #define Py_DEBUG 1 */
が、私が実際に試すと make
時に大量のログが出力されるようになり、そのせい?でコマンドが最後まで終了しませんでした…
大量にログが出始めた時点では python.exe
はすでに出来上がっているので、Ctrl + C
で強制終了すれば一応ステップ実行は可能ですが…(それでも python.exe
を実行すると大量のログが出力されてしまう…)
pyconfig.h
は手動では直さないほうが無難かもしれないです…
configure
に成功すると、最後に下記のようなメッセージが表示されます。
creating Makefile
これが表示されれば configure
の実行は完了です。ワークフォルダ内に 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
の動作確認
次は、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
を保存します。
import
する場合、いつも使用しているモジュールであっても、そのモジュールがインストールされていない or make
されていない可能性があるので注意してください
実行するスクリプトがなんでも良いのであれば、例えば下記のスクリプトをコピペして 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.exe
に test.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 でワークフォルダが開かれるはずです。画面左側のファイルエクスプローラーには下の図のようなファイルやフォルダが表示されるはずです。
launch.json
を編集する
まずは VSCode に、C言語
のデバッグを行おうとしていることを伝えるために、C言語
のソースコードファイルを表示します。
これは、画面左側のファイルエクスプローラーからてきとうな .c
ファイルをクリックして表示すれば良いのですが、今後の説明に都合が良いので下記のファイルを開いていただければと思います。
Programs/python.c
python.c
をファイルエクスプローラーからクリックすれば、下の図のように画面の右側に python.c
の中身が表示されるはずです。
この状態で、続いてメニューから「実行 → デバッグ実行」を選択します。
そうすると、下の図のように、VSCode のインストール済みの C言語
デバッグ関連プラグインの一覧が表示されますので、使用したいプラグインをクリックして選択します。
おそらく、C/C++
プラグイン は “C++ (GDB/LLDB)” で、CodeLLDB
プラグインは “LLDB” で表示されているはずです。もしプラグインを1つしかインストールしていない場合、この画面は表示されないかもしれないです。
C/C++
プラグインの場合は、続いてコンパイラの選択画面が表示されるかもしれないです
この場合は適当なコンパイラを選択してください(これ以降はコンパイルしないので、多分どれ選んでも問題ないと思います)
選択すると、おそらく警告画面が表示されると思います。例えば CodeLLDB
の場合は下の図のような警告が表示されます。が、とりあえずこれは気にせず OK ボタンを押してください。
ボタンを押すと、自動的に launch.json
が VSCode 上に表示されるはずです(上記の操作によって launch.json
が自動的に作成され、それが表示されます)。
C/C++
プラグインの場合は上の図とは異なる警告メッセージが表示されると思います
おそらく中止ボタンが表示されるはずなので、中止ボタンをクリックしてください(とりあえずデバッグ開始をやめれば良い)
そうすると、上記同様に launch.json
が自動的に作成され、launch.json
が VSCode に表示されるはずです
続いて、この launch.json
を編集していきます。ここで編集する目的は下記の3つ(CodeLLDB
の場合は2つ)になります。
- 実行するプログラムファイルを
python.exe
に設定する - 引数に
test.py
を設定する preLaunchTask
を無しにする(C/C++
プラグインの場合)
これらは、下記のように launch.json
を編集することで実現することができます。
"program"
の設定値を"${workspaceFolder}/python.exe"
に設定する"args"
の設定値を["test.py"]
に設定する"preLaunchTask"
の行をコメントアウトする
少し補足しておくと、${workspaceFolder}
部分は VSCode からワークフォルダを開く で開いたフォルダのパスに自動的に置き換えが行われます。
"program"
に設定するのは、デバッグ開始時に実行するプログラムのファイルパスになりますので、1. のように編集することで、VSCode からワークフォルダを開く で開いたフォルダの直下にある python.exe
が実行されるようになります。
もしかしたら、Windows の場合は "${workspaceFolder}/python.exe"
ではなく "${workspaceFolder}¥python.exe"
と記載する必要がある “かも” しれませんので注意してください
また、"args"
に指定したパラメータがデバッグ開始時にプログラムに引数として渡されますので、2. により python.exe
に実行したい Python
スクリプト(test.py
)を指定することになります。
実行したいスクリプトのファイル名に応じて、ここは適宜変更していただければと思います。
ただしこのページでは、python.exe
で実行するスクリプトのファイル名は test.py
として解説していきますので、その点はご注意ください。
さらに、preLaunchTask
は、プログラム実行前にコンパイルを自動的に行う場合に便利ですが、今回はコンパイルは既に make
コマンドにより完了しているため、不要になります。
上記のように編集した場合、CodeLLDB
プラグインの場合は launch.json
が下記のようになるはずです(下記をコピペしてもおそらくうまく動作すると思います)。
{
// 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"
の設定がちょっと不安…)。
{
// 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.c
の main
関数になります(Windows の場合は多分 wmain
)。ここが python.exe
実行時に最初に実行される関数になります。
ということで、main
関数(Windows の場合は wmain
)の中にブレークポイントを貼ってみましょう!
といっても、main
関数の中には1行しかないと思いますので、その行にブレークポイントを設定することになります。その行の、”行番号の左側” をクリックすれば、クリックした位置に赤丸が表示されると思います。
この赤丸が表示されればブレークポイントの設定は完了です!
うまくデバッグ情報を付加した状態で python.exe
が作成されている場合&そのデバッグ情報を付加した際のソースコードを開いている場合、デバッグを開始すれば、そのブレークポイントでプログラムが停止することになります。
ということで、再度デバッグを実行してみましょう!メニューの「実行 → デバッグ開始」からデバッグは実行できますね!(デバッグウィンドウを表示して再生ボタンを押すのでも良いです)。
すると、今度は test.py
が実行される前に、ブレークポイントを設定した行でプログラムが停止するはずです!停止した際には、停止した行が、下の図のように黄色背景になります(色は設定等により異なるかも)。
停止したのであれば…、おめでとうございます!これで Python
本体をステップ実行する環境は整いました!
あとは、あなたが確認したい位置にブレークポイントを設定したり、下の図のデバッグの操作パネルを操作することで、好きな位置のソースコードをステップ実行してやればいいだけです。
例えば、上の図の状態から、デバッグの操作パネルからステップイン(下矢印のボタン)をクリックすれば、停止した位置から実行しようとしている関数(上の図の場合は Py_BytesMain
関数)の中に入って一行ずつプログラムを停止しながら、この関数の中の動作を調べることができます。
また、画面左側のウィンドウから、変数の中身やコールスタック(関数がどのように呼ばれて現在の処理の位置まで到達したかの履歴)を確認することも可能です。
デバッグのやり方の詳細は下記のページで紹介していますので、これらを駆使していろんなステップ実行を行なってみてください!
VSCodeでMacOSにC言語デバッグ環境を構築もし、どこにブレークポイントを設定してもプログラムが停止しないという場合、下記を確認してみてください。
configure
実行時に--with-pydebug
オプションを指定したかどうか- おそらくこのオプションを付けないと最適化が効いてしまって、プログラムが停止しなくなると思います
make
実行後にソースコードの位置(フォルダ)を変更していないか- コンパイル時に付加されるデバッグ情報にはソースコードへのパスが記述されているはずです
- なので、ソースコードの位置を変更してしまうとデバッグ情報とソースコードのパストで話が合わなくなって、ブレークポイントがうまく設定できなくなります
スポンサーリンク
いろんな位置にブレークポイントを設定してみる
といっても、ソースコードの規模も大きいのでどこにブレークポイントを貼れば良いかを判断するのが難しいですよね…。
ということで、どのあたりにブレークポイントを設定すれば良いかについて、いくつかの例を挙げて紹介していきたいと思います。
ただ、私もまだ解析しきれていないので、文字列検索等を駆使しながら、是非皆さんもブレークポイントをいろんなところに設定して動作確認をしてみてください。
ここに記載している内容は私がステップ実行しながら解析した内容になりますので、間違っている可能性もあるので注意してください
また、Python のバージョンや取得したソースコードのリビジョンが異なる場合、ソースコードの構成も異なる可能性があるので注意してください(私は 3.9.7 を使用)
組み込み関数
組み込み関数(例えば print
関数や input
関数)は、下記のファイルで定義されているようです。
Python/bltinmodule.c
例えば、Python
スクリプトで print
関数が実行された際には、上記ファイルの builtin_print
関数が呼ばれます。ですので、builtin_print
の先頭付近にでもブレークポイントを設定しておけば、print
関数実行時の動作をステップ実行しながら確認することができると思います。
また、input
関数の場合は builtin_input_impl
関数が呼ばれるはずです。
その他の組み込み関数の多くも、上記のファイルで builtin_xxx
や builtin_xxx_impl
などの関数名で定義されていますので、この辺りがブレークポイントを設定する位置の目安になるかと思います(例えば max
や min
、pow
などの関数の実装もこのファイル内に存在します)。
標準モジュールの関数
標準モジュールの関数は Modules
フォルダ内のファイルで定義されているようです。
例えば、random
モジュールの関数は下記ファイルで定義されているようです。
Modules/_randommodule.c
Python
スクリプトから random
関数(random.random
)を実行すれば、_randommodule.c
の _random_Random_random_impl
関数が呼ばれることが確認できました。なので、この関数にブレークポイントを設定し、そこからステップ実行していけば、どのようにランダム値が生成されているかがわかると思います。
csv
モジュールの関数は下記ファイルで定義されているようです。
Modules/_csv.c
Python
スクリプトから reader
関数(csv.reader
)を実行すれば、_csv.c
の csv_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
でプログラムが停止するはずです。
で、この時に、上の図のようにデバッグウィンドウの変数欄から、 ob_digit
に足し算を行なっている値 9876
と 54321
が格納されていることが確認できると思います(画面小さいと見えないかも…)。
ただ、この long_add
関数は、Python
スクリプト実行前の Python
の初期化処理?でも大量に呼ばれてしまうので、ブレークポイントを有効にするタイミングは要検討だと思います。
私は Python
の初期化処理が終わった後に、ブレークポイントを有効にして上の図のようにプログラムが停止することを確認しました。より具体的にいうと、Python/pythonrun.c
の pyrun_file
関数にあらかじめブレークポイントを設定しておき、ここでプログラムが停止した後に(この時点では Python
の初期化は完了している)、long_add
関数に設定したブレークポイントを有効にするようにしています。
ただし、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
本体では下記の構造体として扱われていると思います。
struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};
ガベージコレクション
ガベージコレクションは、下記ファイルの _Py_DECREF
関数で行われているようです。
include/object.h
この関数の中で、オブジェクトの参照回数を 1
減らし、0
になった場合は _Py_Dealloc
が呼ばれて、そこからメモリの解放が行われるようです。
例えば自身で定義したクラスのオブジェクトへの参照がなくなった際には、_Py_Dealloc
が呼ばれ、下の図のように、op.obtype.tpname
に、そのクラス名(下の図の場合は DaeuClass
)が格納されたオブジェクトが解放される様子が確認できるのではないかと思います。
ただメモリの解放では、free
関数が実行されるケースもあれば、メモリプールに使用していたメモリを返却することで解放するケースもあるようでした。
また、当然この _Py_DECREF
も大量に呼ばれる関数なので、ブレークポイントを有効にするタイミングには注意してください。
スクリプトの実行
ステップ実行で動作を追った感じだと、おそらく事前にスクリプトを解釈した結果に基づき、下記ファイルの _PyEval_EvalFrameDefault
関数の中でループを回しながら、スクリプトに記述された処理を実行しているように見えました(mainloop:
ラベル付近の無限ループ内で)。
Python/ceval.c
じゃあスクリプトの解釈はどこで行なっているのかというと、すみません、良く分かってないです。
おそらく、スクリプトを下記フォルダのファイルの関数群でパースして、
Parser
その結果を下記ファイルで中間言語にコンパイルして、
Python/compile.c
で、そのコンパイルした結果に基づいて _PyEval_EvalFrameDefault
関数から Python
スクリプトに記述された処理を実行しているのではないかなぁと思います。
ちょっとはっきり分かっていないので申し訳ないのですが、Python
の動作を解析する時の参考になれば幸いです。
まとめ
このページでは、Python
本体をステップ実行するための方法について解説しました。
ステップ実行することで、Python
本体の動きをより解析しやすくなると思います!特にプログラミング言語がどのように実装されているかを知りたい方にはオススメの内容になっています。
ステップ実行するまでの流れは、大きく分けると下記の3つになります。
特に2つ目の Python のビルド 時に、デバッグ情報を付加させる&最適化を OFF にするというところがポイントになると思います。
で、上記の流れを踏襲すれば、C言語
で実装されているのであれば、他のプログラミング言語も同様にステップ実行できる環境を整えることができると思います。
また、プログラミング言語だけでなく、ライブラリ等も同様にステップ実行できるようになるので、ぜひこのステップ実行するまでの流れは覚えておいてください!