tl;dr
十分にテストされた回答については、以下の「完璧を求めて」サブセクションを参照してください。
abarnertによる移植可能な C 拡張機能の識別に関わる微妙な分析に対する実用的な対比として、Stack Overflow Productions™ は...実際の答えを提示します。
C 拡張機能と非 C 拡張機能を確実に区別できる能力は非常に有用であり、これがなければ Python コミュニティは貧しくなります。実際の使用例は次のとおりです。
- アプリケーションがフリーズし、 1 つのクロスプラットフォーム Python コードベースが複数のプラットフォーム固有の実行可能ファイルに変換されます。PyInstallerは、ここでの標準的な例です。C 拡張機能を特定することは、堅牢な凍結に不可欠です。凍結されているコードベースによってインポートされたモジュールが C 拡張である場合、その C 拡張によって推移的にリンクされたすべての外部共有ライブラリも、そのコードベースで凍結する必要があります。恥ずべき告白: 私は PyInstallerに貢献しています。
- アプリケーションの最適化。ネイティブ マシン コード ( Cythonなど)に対して静的に、またはジャスト イン タイム方式 ( Numbaなど) で動的に行います。自明の理由から、Python オプティマイザーは、コンパイル済みの C 拡張機能とコンパイルされていない純粋な Python モジュールを必然的に区別します。
- 依存関係の分析、エンド ユーザーに代わって外部共有ライブラリを検査します。私たちの場合、必須の依存関係 ( Numpy ) を分析して、並列化されていない共有ライブラリ (参照 BLAS 実装など) にリンクしているこの依存関係のローカル インストールを検出し、その場合にエンド ユーザーに通知します。なんで?制御できない依存関係の不適切なインストールが原因でアプリケーションのパフォーマンスが低下した場合、責任を負いたくないからです。悪いパフォーマンスはあなたのせいです、不運なユーザーです!
- おそらく他の重要な低レベルのもの。もしかしてプロファイリング?
フリーズ、最適化、およびエンド ユーザーの苦情の最小化が有用であることは、誰もが認めるところです。したがって、C 拡張機能を特定することは役に立ちます。
意見の相違が深まる
abarnertの最後から 2 番目の結論にも同意しません。
このために誰もが思いついた最高のヒューリスティックは、inspect
モジュールに実装されているものなので、それを使用するのが最善です。
いいえ。これについて誰もが思いついた最高のヒューリスティックは、以下に示すものです。すべての stdlib モジュール (を含むがそれに限定されないinspect
) は、この目的には役に立ちません。具体的には:
inspect.getsource()
および関数は、C 拡張機能 (当然のことながら純粋な Python ソースを持たない) と、純粋な Python ソースを持たない他のタイプのモジュール (バイトコードのみのモジュールなど) の両方に対してinspect.getsourcefile()
曖昧に返されます。役に立たない。None
importlib
machinesは、 PEP 302 準拠のローダーによってロード可能なモジュールにのみ適用されるため、デフォルトのインポート アルゴリズムに表示されます。便利ですが、一般的にはほとんど適用できません。PEP 302 に準拠しているという仮定は、現実の世界でパッケージが何度も直面すると崩れます。たとえば、ビルトインが実際にオーバーライド可能であることをご存知ですか? これは、Python のインポート メカニズムをカスタマイズするために使用した方法です。地球がまだ平らだった頃にさかのぼります。importlib
__import__()
abarnertの最終的な結論にも論争があります。
…完璧な答えはありません。
完璧な答えがあります。よく疑われるハイラルの伝説のトライフォースのように、すべての不完全な質問に対して完璧な答えが存在します。
見つけてみましょう。
完璧を求めて
次の純粋な 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 pure-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 拡張です。
- それ以外の場合は、(A)アクティブな Python インタープリターが公式の CPython 実装 (例: PyPy )ではないか、 (B)アクティブな Python インタープリターは CPython ですが、このモジュールはPEP 302 準拠のローダーによってロードされませんでした。オーバーライドされている機械 (たとえば、この Python アプリケーションをプラットフォーム固有の凍結されたバイナリとして実行する低レベルのブートローダーによって)。いずれの場合も、このモジュールのファイルタイプが現在のプラットフォームに固有の C 拡張のファイルタイプであるかどうかのテストにフォールバックします。
__import__()
8 行の機能と 20 ページの説明。それがまさに私たちが転がる方法です。