Pythonの「__pycache__」「*.pyc」「*.pyo」「*pyd」について調べてみた
Pythonスクリプトを実行した後、実行時のディレクトリに「__pycache__」というディレクトリができており、何のことか気になったので調べてみました。
__pycache__とは
Pythonスクリプトの実行時、コンパイルされたモジュールをキャッシュすることで、再度呼び出す処理を高速化するための仕組みのようです。
同じプログラムを再実行するとき、この「__pycache__」があればモジュール等を再コンパイルする必要がなくなるので、読み込みを高速化することができます。
コンパイル結果のキャッシュなので、消してもプログラムは問題なく実行できます。
一方でキャッシュがなくなるので、再コンパイルの必要になる分プログラムの実行は遅くなります。
__pycache__の中にできるもの
「__pycache__」はディレクトリであり、中にコンパイルされたモジュールが保存されます。
その拡張子には、以下のようなものがあります。
*.pyc
コンパイルされたバイトコードのこと。呼び出しプログラムからimport文で読み込んだモジュールはキャッシュされ、この形式で保管されます。
ちなみに命名規則もあります。
例えば、CPython(Pythonのコンパイルをしているプログラム)
のリリース3.5によってコンパイルされたsample.pyは
「__pycache__/sample.cpython-35.pyc」
としてキャッシュされるでしょう。
*.pyo
pythonコマンドで最適化オプション
- -O(基本的な最適化)
- -OO(上記に加えてドキュメントコードも除去するオプション)
により最適化された際に作成されるバイトコードのこと。
*.pyd
PythonファイルからできるWindowsのdllファイルのこと。Windowsはあまり使っていないので詳しくは公式のQ&Aからお願いいたします。
Pythonの対話コンソールからコンパイルしてみる
「py_compile」というライブラリを使えば、任意のPythonファイルをコンパイルし、結果コードを_
_pycache__内に生成できます。
$ python
Python 3.5.6 (default, Sep 28 2019, 10:29:54)
[GCC 7.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import py_compile
>>> py_compile.compile('../コンパイルしたいファイル名.py')
'../__pycache__/コンパイルしたいファイル名.cpython-35.pyc'
>>> exit()
$ ls ./__pycahe__
server.cpython-35.pyc
できました。
コンパイル済みバイトコード(*.pyc)を実行してみる
コンパイル済みコードの準備
試しにprint文だけ記載したhello.pyを作成します。
- hello.py
print("hello, world")
対話コンソールからコンパイルします。
>>> import py_compile
>>> py_compile.compile("hello.py")
'../__pycache__/hello.cpython-35.pyc'
>>> exit()
作成完了したら対話コンソールを抜けましょう。
コンパイル済みPythonコードの実行
実はこの*.pycファイルはキャッシュコードとしてpythonコマンドで認識でき、実行可能です。
よって以下のようにpythonコマンドの引数にコンパイル済みコードを渡すと…
$ python ./__pycache__/hello.cpython-35.pyc
hello, world
ちゃんと.pyのやつと同じ結果を返してくれます。
__pycache__や*.pycを作成させない方法
Pythonスクリプト実行時にオプション「-B」をつけると、これらのキャッシュ機能をオフにできます。
$ python -B sample.py
このようにしてファイルを実行すれば、内部で呼び出ししているモジュールのキャッシュはされないようです。
__pycache__の更新チェックについて
Pythonはソースの変更日時をコンパイル済みのものと比較し、コンパイル済みのものが最新でなくなるなど、再コンパイルが必要な状態かどうかを確認します。
これはPythonによって完全に自動で処理されます。
今からコンパイルしようとしているプログラムと__pycache__内にある対応コードを比較し、
変わりがなければキャッシュを利用
変わっていれば再コンパイルしバイトコードを作成する
ようです。
また、コンパイル済みモジュールはプラットフォームに依存しないため、アーキテクチャの異なるシステム間でも同じライブラリを共有することもできます。
最後に
__pycache__について調査してみると、
- キャッシュとして利用される
- 消してしまっても実行に支障がないこと
が分かりました。
普段あまり触らないかもしれないですが、仕組みを知っておくことで今後役に立つかもしれません。
それでは、また会いましょう!