Pythonからインポートされたモジュールが他のモジュールから来たものであるかどうかを判断する正しい、または最も堅牢な方法は何ですか?C拡張純粋な Python モジュールとは対照的に、これは、たとえば、Python パッケージに純粋な Python 実装と C 実装の両方のモジュールがあり、実行時にどちらが使用されているかを確認したい場合に便利です。
1 つのアイデアとしては、 のファイル拡張子を調べることですmodule.__file__
が、すべてのファイル拡張子をチェックする必要があるのか、またこの方法が必ずしも最も信頼できるのかどうかはわかりません。
ベストアンサー1
要約
十分にテストされた答えについては、以下の「完璧を求めて」のサブセクションを参照してください。
実用的な対比としてアバーナートの役に立つ分析C 拡張機能を移植可能に識別する際の微妙な点について、Stack Overflow Productions™ が紹介します...実際の答え。
C 拡張と非 C 拡張を確実に区別する機能は非常に有用であり、これがなければ Python コミュニティは貧弱になってしまいます。実際の使用例は次のとおりです。
- アプリケーションのフリーズ、1 つのクロスプラットフォーム Python コードベースを複数のプラットフォーム固有の実行可能ファイルに変換します。Pyインストーラはここでの標準的な例です。C拡張機能を識別することは、堅牢な凍結にとって重要です。凍結されるコードベースによってインポートされたモジュールがC拡張機能である場合、そのC拡張機能によって推移的にリンクされているすべての外部共有ライブラリしなければならないそのコードベースも凍結されます。恥ずかしい告白:私は貢献するPyInstaller へ。
- アプリケーションの最適化、ネイティブマシンコードに静的に変換するか(例:シトン)またはジャストインタイム方式で動的に(例:ナンバ) 自明の理由により、Python オプティマイザは、コンパイル済みの C 拡張モジュールとコンパイルされていない純粋な Python モジュールを必然的に区別します。
- 依存関係分析、エンドユーザーに代わって外部共有ライブラリを検査します。私たちの場合、我々は必須の依存関係(ナンピー)を使用して、非並列化共有ライブラリ(例えば、参照BLAS実装) を作成し、その場合はエンド ユーザーに通知します。なぜでしょうか。制御できない依存関係が不適切にインストールされたためにアプリケーションのパフォーマンスが低下した場合、責任を負わされたくないからです。パフォーマンスが悪いのはあなたのせいです、不運なユーザーさん!
- おそらく他の重要な低レベルのもの。プロファイリングでしょうか?
フリーズ、最適化、エンドユーザーの苦情を最小限に抑えることが有用であることは、誰もが認めるところです。したがって、C 拡張機能を識別することは有用です。
意見の相違は深まる
私も反対ですアバーナートの最後から2番目の結論は次のとおりです。
これに関して誰かが考え出した最良のヒューリスティックは
inspect
モジュールに実装されているものなので、それを使用するのが最善です。
いいえ。これに関して誰かが思いついた最良のヒューリスティックは、以下に示すとおりです。すべてのstdlibモジュール(ただしない限定的inspect
)はこの目的には役に立ちません。具体的には、
- 関数
inspect.getsource()
とinspect.getsourcefile()
関数は、C 拡張 (当然ながら純粋な Python ソースはありません) と、同じく純粋な Python ソースがない他の種類のモジュール (バイトコードのみのモジュールなど) の両方に対して、あいまいな値を返しNone
ます。使い物にならない。 importlib
機械のみロード可能なモジュールに適用されますPEP 302準拠のローダーしたがって、デフォルトのimportlib
インポート アルゴリズムに表示されます。役に立つ、しかし、一般的にはほとんど適用できません。PEP 302準拠の仮定は、現実世界があなたのパッケージに何度もぶつかると崩れてしまいます。例えば、__import__()
組み込み関数が実際には上書き可能?これは、地球がまだ平らだった頃に Python のインポート メカニズムをカスタマイズしていた方法です。
…完璧な答えはありません。
完璧な答えがあります。よく疑われるトライフォースハイラルの伝説によれば、不完全な質問に対しては完璧な答えが存在します。
見つけてみましょう。
完璧を求めて
次の純粋な Python 関数は、True
渡された以前にインポートされたモジュール オブジェクトが C 拡張である場合にのみ返されます。簡単にするために、Python 3.xと想定されます。
import inspect, os
from importlib.machinery import ExtensionFileLoader, EXTENSION_SUFFIXES
from types import ModuleType
def is_c_extension(module: ModuleType) -> bool:
'''
`True` only if the passed module is a C extension implemented as a
dynamically linked shared library specific to the current platform.
Parameters
----------
module : ModuleType
Previously imported module object to be tested.
Returns
----------
bool
`True` only if this module is a C extension.
'''
assert isinstance(module, ModuleType), '"{}" not a module.'.format(module)
# If this module was loaded by a PEP 302-compliant CPython-specific loader
# loading only C extensions, this module is a C extension.
if isinstance(getattr(module, '__loader__', None), ExtensionFileLoader):
return True
# Else, fallback to filetype matching heuristics.
#
# Absolute path of the file defining this module.
module_filename = inspect.getfile(module)
# "."-prefixed filetype of this path if any or the empty string otherwise.
module_filetype = os.path.splitext(module_filename)[1]
# This module is only a C extension if this path's filetype is that of a
# C extension specific to the current platform.
return module_filetype in EXTENSION_SUFFIXES
長く見えるのは、docstring、コメント、アサーションが優れているためです。実際には 6 行だけです。グイド、年寄りの心は痛いほど分かるよ。
実績が証明する
移植可能なインポート可能な 4 つのモジュールを使用して、この関数を単体テストしてみましょう。
- stdlib の純粋な Python
os.__init__
モジュール。C 拡張ではないことを願います。 - stdlib の純粋な Python
importlib.machinery
サブモジュール。C 拡張ではないことを願います。 - stdlib
_elementtree
C 拡張。 - サードパーティの
numpy.core.multiarray
C 拡張機能。
つまり:
>>> import os
>>> import importlib.machinery as im
>>> import _elementtree as et
>>> import numpy.core.multiarray as ma
>>> for module in (os, im, et, ma):
... print('Is "{}" a C extension? {}'.format(
... module.__name__, is_c_extension(module)))
Is "os" a C extension? False
Is "importlib.machinery" a C extension? False
Is "_elementtree" a C extension? True
Is "numpy.core.multiarray" a C extension? True
終わりよければすべてよし。
これを行う方法?
コードの詳細はさほど重要ではありません。では、どこから始めましょうか。
- 渡されたモジュールがPEP 302準拠のローダーによってロードされた場合(よくあるケース)、PEP 302 仕様このモジュールへのインポート時に割り当てられる属性には、
__loader__
このモジュールをロードするローダー オブジェクトを値とする特別な属性を定義する必要があります。したがって、- このモジュールのこの値が CPython 固有の
importlib.machinery.ExtensionFileLoader
クラスのインスタンスである場合、このモジュールは C 拡張です。
- このモジュールのこの値が CPython 固有の
- そうでなければ、(あ)アクティブなPythonインタープリターはない公式のCPython実装(例:ピピ) または(バ)アクティブなPythonインタープリターはCPythonですが、このモジュールはないPEP 302 準拠のローダーによってロードされます。通常は、デフォルトの仕組みがオーバーライドされているためです
__import__()
(たとえば、この Python アプリケーションをプラットフォーム固有の凍結バイナリとして実行する低レベルのブートローダーによって)。いずれの場合も、このモジュールのファイルタイプが現在のプラットフォームに固有の C 拡張機能であるかどうかをテストするためのフォールバックが行われます。
8 行の機能と 20 ページの説明。それが私たちのやり方です。