Pythonコードの解析とpycファイル
このガイドでは、PythonファイルがGraalVM Pythonランタイムでどのように解析されるかについて詳しく説明します。
パーサーのパフォーマンス
シリアライズされた.pyc
ファイルからコードをロードする方が、ANTLRを使用して.py
ファイルを解析するよりも高速です。
Pythonソースの抽象構文ツリー(AST)の作成には、次の2つのフェーズがあります。最初のフェーズでは、単純な構文ツリー(SST)とスコープ・ツリーが作成されます。2番目のフェーズでは、SSTが言語実装フレームワーク・ツリーに変換されます。
変換には、スコープ・ツリーが必要です。スコープ・ツリーには、変数および関数定義のスコープ位置と、スコープに関する情報が含まれています。単純な構文ツリーには、ソースをミラーリングするノードが含まれています。SSTと言語実装フレームワーク・ツリーを比較すると、SSTの方がはるかに小さくなります。これには、単純な方法でソースを表すノードのみが含まれています。通常は、1つのSSTノードが多数の言語実装フレームワーク・ノードに変換されます。
単純な構文ツリーは、ANTLR解析または適切な*.pyc
ファイルからのデシリアライズという2つの方法で作成できます。ソースについて適切な.pyc
ファイルがない場合は、ANTLRを使用してソースが解析されます。Pythonの標準のインポート・ロジックで適切な.pyc
ファイルが見つかった場合は、単にそれからのSSTとスコープ・ツリーのデシリアライズがトリガーされます。
デシリアライズは、ANTLRを使用したソース解析よりもはるかに高速で、所要時間はANTLRが必要とする時間のわずか約30%です。当然ながら、新しいファイルの最初のインポートには少し時間がかかります。ANTLRを使用した解析に加えて、Pythonの標準のライブラリ・インポート・ロジックでは、結果のコード・オブジェクトが.pyc
ファイルにシリアライズされます。つまり、ここでは、SSTとスコープ・ツリーがこのようなファイルにシリアライズされます。
pycファイルの作成および管理
目的の.py
ファイルと一致する.pyc
ファイルが見つからないか、無効なファイルが見つかった場合、.pyc
ファイルはGraalVM Pythonランタイムによって自動的に作成されます。
Pythonソース・ファイル(モジュール)が最初の実行中にインポートされたときに、適切な.pyc
ファイルが自動的に作成されます。同じモジュールが再度インポートされたときには、すでに作成されている.pyc
ファイルが使用されます。つまり、まだ実行(インポート)されていないソース・ファイルについては.pyc
ファイルはありません。.pyc
ファイルの作成は完全にFileSystem APIを介して行われ、埋込み担当者はファイルシステム・アクセスを管理できます。
その後、スクリプトを実行するたびに、既存の.pyc
ファイルが再利用されるか、新しいものが生成されます。元のソース・ファイルのタイムスタンプまたはハッシュコードが変更されると、.pyc
ファイルが再生成されます。ハッシュコードは、source.hashCode()
をコールすることによってPythonソースに基づいてのみ生成され、java.util.Arrays.hashCode(byte[])
で計算された、ソース・ファイル・バイトの配列に対するJDKハッシュコードです。
.pyc
ファイルは、Pythonパーサーのマジック番号が変更された場合にも再生成されます。マジック番号はPythonソースでハードコードされており、ユーザーが変更することはできません(当然ながら、そのユーザーがPythonのバイトコードへのアクセス権を持っている場合を除きます)。
SSTまたはスコープ・ツリーのバイナリ・データの形式が変更されると、GraalVMのPythonランタイムの開発者はマジック番号を変更します。これは実装の詳細であるため、マジック番号はGraalVMのPythonランタイムのバージョンに対応している必要はありません(CPythonの場合と同様)。pycのマジック番号は、実行されている具体的なPythonランタイムJavaコードに応じて変わります。
.pyc
ファイルを使用する場合、バージョンを切り替えるときまたは元のソース・コードを変更するときには少なくともGraalVMのPythonランタイムへの書込みアクセスを許可する必要があります。そうしないと、ソース・ファイルの再生成が失敗し、インポートのたびに、古い.pyc
ファイルへのアクセス、コードの解析、そのシリアライズ、および新しい.pyc
ファイルの記述の試行(および失敗)というオーバーヘッドが発生します。
*.pyc
ファイルは、GraalVMのPythonランタイムによって削除されることはなく、再生成されるのみです。これは、該当するソース・ファイルが変更されたとき(内容の最終変更のタイムスタンプまたはハッシュコード)、またはPython実装パーサーのマジック番号が変更されたときに再生成されます。マジック番号の変更はリリース・ノートで通知されるため、埋込み担当者またはシステム管理者はアップグレード時に古い.pyc
ファイルを削除できます。
.pyc
ファイルについて作成されるフォルダ構造は次のようになります:
top_folder
__pycache__
sourceA.graalpython.pyc
sourceB.graalpython.pyc
sourceA.py
sourceB.py
sub_folder
__pycache__
sourceX.graalpython.pyc
sourceX.py
デフォルトでは、__pycache__
ディレクトリはソース・コード・ファイルと同じディレクトリ・レベルに作成され、このディレクトリに、同じディレクトリのすべての.pyc
ファイルが格納されます。このフォルダには、異なるバージョンのPython (CPythonなどを含む)で作成された.pyc
ファイルが格納されることがあるため、ユーザーには、*.cpython3-6.pyc
などで終わるファイルが表示される場合があります。
現在の実装では、.pyc
ファイルに元のソース・テキストのコピーも含まれます。これは軽微なパフォーマンス最適化であるため、元のソース・ファイルへのパスを使用してSource
オブジェクトを作成できますが、元の*.py
ファイルを読み取る必要はありません。これにより、言語実装フレームワーク・ツリーを取得するプロセスが高速化されます(1つのファイルのみが読み取られます)。.graalpython.pyc
ファイルの構造は次のとおりです:
MAGIC_NUMBER
source text
binary data - scope tree
binary data - simple syntax tree
元のソースを引き続きリカバリできるため、.pyc
ファイルは、ゲスト・コードからPythonライブラリ・ソース・コードを見えなくするための効果的な手段ではありません。ソースが省略されていても、構文ツリーには十分な情報が含まれているため、簡単にソース・コードに逆コンパイルできます。
シリアライズされたSSTおよびスコープ・ツリーは、属性co_code
(CPythonではバイトコードを含む)の内容として、Pythonのcode
オブジェクトにも格納されます。たとえば:
>>> def add(x, y):
... return x+y
...
>>> add.__code__.co_code
b'\x01\x00\x00\x02[]K\xbf\xd1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 ...'
.pyc
ファイルは主に、CPythonと互換性のある方法でランタイムによって自動的に管理されます。CPythonの場合と同様に、それらの場所と、それらを記述する必要があるかどうかを指定するオプションがあり、これらのオプションの両方をゲスト・コードで変更できます。
*.pyc
ファイルの作成は、CPythonの場合と同じ方法で制御できます(https://docs.python.org/3/using/cmdline.htmlを参照):
- GraalVMのPythonランチャ(
graalpython
)は、PYTHONDONTWRITEBYTECODE
環境変数を読み取ります。これが空でない文字列に設定されている場合、Pythonはモジュールのインポート時に.pyc
ファイルの記述を試行しません。 - ランチャのコマンドライン・オプション
-B
を指定した場合、前述のものと同じ効果があります。 - ゲスト言語コードは、実行時に
sys
組込みモジュールの属性dont_write_bytecode
を変更して、後続のインポートの動作を変更できます。 - ランチャは、
PYTHONPYCACHEPREFIX
環境変数を読み取ります。設定すると、接頭辞が指すパスに__pycache__
ディレクトリが作成され、ソース・ツリーのディレクトリ構造のミラーがオンデマンドで作成されて.pyc
ファイルが格納されます。 - ゲスト言語コードは、実行時に
sys
モジュールの属性pycache_prefix
を変更して、後続のインポートの場所を変更できます。
埋込み担当者は、環境変数またはCPythonオプションを使用してこれらのオプションをGraalVMのPythonの実装に通信することはできないため、これらのオプションを次の言語オプションとして使用できます:
python.DontWriteBytecodeFlag
--B
またはPYTHONDONTWRITEBYTECODE
と同等python.PyCachePrefix
-PYTHONPYCACHEPREFIX
と同等
Pythonコンテキストでは、.pyc
ファイルの記述がデフォルトで有効になりません。graalpython
ランチャによってこれがデフォルトで有効になりますが、埋込みユースケースでこれが必要な場合は、__pycache__
の場所が適切に管理され、その場所にあるファイルが導出元のソース.py
ファイルと同様に操作に対して保護されるように注意を払う必要があります。
また、アプリケーション・ソースをGraalVM EntepriseのPythonランタイムにアップグレードするには、埋込み担当者が必要に応じて古い.pyc
ファイルを削除する必要があります。
セキュリティに関する考慮事項
SSTおよびスコープ・ツリーのシリアライズは手動で記述され、デシリアライズ中にSSTノード以外のクラスをロードすることはできません。Javaオブジェクトのシリアライズには、Javaシリアライズまたはその他のフレームワークは使用されません。その主な理由はパフォーマンスですが、これには、悪意を持って作成された.pyc
ファイルによってクラスのロードを強制できないという効果があります。
ファイル操作(データおよびタイムスタンプの取得やpyc
ファイルの記述)はすべて、FileSystem APIを介して行われます。埋込み担当者は、カスタム(読取り専用など)のFileSystem
実装を使用して、これらの操作すべてを変更できます。また、埋込み担当者は、GraalVMのPythonランタイムについてI/O権限を無効にすることにより、.pyc
ファイルの作成を事実上無効にすることもできます。
.pyc
ファイルが読取り可能でない場合、その場所は書込み可能ではありません。.pyc
ファイルのシリアライズ・データまたはマジック番号がなんらかの形で破損している場合、デシリアライズは失敗し、単に.py
ファイルが再度解析されます。これによって、モジュールの解析についてのみ、パフォーマンスにわずかな影響が生じますが、ほとんどのアプリケーションにとって重大ではありません(アプリケーションがPythonコードのロード以外に実際の作業を行う場合)。