"プログラムはなぜ動くのか"という本を読んで、プログラムがどのように実行されるのか勉強しています。
C++
はコンパイラ言語なので、g++ -o hello main.cpp
のようにコンパイルしてマシン語に変換します。
では、インタプリタ言語の一種であるPython
だと、どのようにマシン語に変換しているのか気になったので調べてみました。
- 実行環境
$ python3 --version
Python3.7.3
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.4
BuildVersion: 18E226
$ ls
main.py utils/ keywords/
ソースコードをコンパイルする
Pythonの公式ドキュメントを読むと、2通りの方法があるらしいことがわかりました。
コンパイルするファイルは、自作のmain.pyを使いました。
compileall を使う
コンパイルしてみる。
$ python3 -m compileall main.py
出力結果は、__pycache__/main.cpython-37.pyc
として保存されている。
バイトコード ・ファイルが出来上がるので、読みやすい形式にして出力する。
$ xxd -bits __pycache__/main.cpython-37.pyc
py_compile を使う
コンパイルしてみる。
$ python3 -m py_compile main.py
こちらも、出力結果は、__pycache__/main.cpython-37.pyc
として保存されている。
バイトコード ・ファイルが出来上がるので、読みやすい形式にして出力する。
$ xxd -bits __pycache__/main.cpython-37.pyc
compileall と py_compile の差異
importしているモジュールを格納しているディレクトリ内のソースコードをコンパイルしてみる。
compileall
だとコンパイルできる。
$ python3 -m compileall utils/
Listing 'utils/'...
Compiling 'utils/auth.py'...
Compiling 'utils/cache.py'...
Compiling 'utils/fetch.py'...
Compiling 'utils/folder.py'...
Compiling 'utils/generator.py'...
Compiling 'utils/sheet.py'...
一方、py_compile
だとコンパイルできない。
$ python3 -m py_compile utils/
Traceback (most recent call last):
File "/Users/Tachikoma/.pyenv/versions/3.7.3/lib/python3.7/runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "/Users/Tachikoma/.pyenv/versions/3.7.3/lib/python3.7/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/Users/Tachikoma/.pyenv/versions/3.7.3/lib/python3.7/py_compile.py", line 212, in <module>
sys.exit(main())
File "/Users/Tachikoma/.pyenv/versions/3.7.3/lib/python3.7/py_compile.py", line 204, in main
compile(filename, doraise=True)
File "/Users/Tachikoma/.pyenv/versions/3.7.3/lib/python3.7/py_compile.py", line 140, in compile
source_bytes = loader.get_data(file)
File "<frozen importlib._bootstrap_external>", line 916, in get_data
IsADirectoryError: [Errno 21] Is a directory: 'utils/'
中間生成ファイルはCFileとして出力される
Pythonの公式ドキュメントにも記述されていたように、コンパイル結果として、[CFile](https://docs.microsoft.com/en-us/cpp/mfc/reference/cfile-class?view=vs-2019)
が生成されている。
CFile
とは、MFCファイルの基底クラスらしい。
デコンパイルしてみる
.pyc
-> .py
のデコンパイルをしてみる。
こちらのサイトを使って、デコンパイルすると、compileall
, py_compile
のどちらの場合でも、出力結果は同じとなった。
中間生成ファイルを実行してみた
main.cpython-37.pyc
を実行できないかと思い、試してみた。
$ python3 __pycache__/main.cpython-37.pyc
Traceback (most recent call last):
File "main.py", line 10, in <module>
import keywords.InputSearchKeywordsFromConsole as Keywords
ModuleNotFoundError: No module named 'keywords'
importしているモジュールがないと怒られた。
__pycache__/keywords/InputSearchKeywordsFromConsole.py
__pycache__/keywords/InputSearchKeywordsFromConsole.cpython-37.pyc
どちらも試してみたが、うまくいかなかった。
__pycache__
とは
python3 main.py
実行時に、importしているモジュールに対して、自動的に生成される。
$ ls
main.py utils/
$ ls utils/
fetch.py
$ python3 main.py
$ ls
main.py utils/
$ ls utils/
__pycache__/ fetch.py
追記
dis を使って逆アセンブルしてみる
@shiracamus 様にコメント頂いたので、実際にコマンドを打ってみました。
ありがとうございます。
逆アセンブルして、テキストファイルに出力してみる。
$ python3 -m dis main.py > __pycache__/dis.txt
公式ドキュメントをみると、
26 100 LOAD_NAME 17 (print)
左から、逆アセンブルする前の行番号
, 命令のアドレス
, バイトコード命令
, 命令パラメタ
, 命令パラメタの解釈
を意味している。
バイトコード命令LOAD_NAME
は、プログラムはなぜ動くのか
の第1章で出てきたアセンブリ言語のニーモニックに似ている。
python3 -m の -m について
公式ドキュメントにあるように、
$ python3 -m <module> main.py
とすると、指定したmoduleをimportすることができます。