pythonのモジュールシステムを深く理解する

5825 ワード

pythonのモジュールシステムを深く理解する
pythonエンジニアリングのコードは、モジュールとパッケージの形式で組織されています.要約すると、pythonのファイルは実行時にモジュールに対応し、init.pyを含むフォルダはパッケージに対応します.1つのモジュールの他のモジュールまたはパッケージ内のコンテンツへの参照はimportキーワードによって実現される.規模の大きいプロジェクト開発では,異なるモジュール,パケット間の参照関係,および検索パスがしばしば悩まされる.したがって,pythonモジュールの本質を認識し,python下位importの処理メカニズムを整理することは有意義である.
本文はpythonのモジュールシステムを4つの部分に分けて整理する.第1部ではpython moduleオブジェクトの具体的なデータ構造について説明します.第2部ではpythonのパッケージとモジュールについて説明します.第3部はimport文の下位論理を詳しく整理する.最後のセクションでは、pythonのimportメカニズムをめぐって、開発中の詳細について説明します.
PyModuleObjectオブジェクト
pyファイルに対応するモジュールでも、フォルダに対応するパッケージでも、pythonの最下位実装では、PyModuleObjectタイプのオブジェクトで記述されます.
typedef struct {
    PyObject_Head
    PyObject *md_dict
}PyModuleObject;

PyModuleObjectオブジェクトの本体は辞書オブジェクトであることがわかります.ここから私たちは見ることができる.実はpython moduleはpython辞書です.より高いレベルで見ると、python moduleは主に様々なオブジェクト(変数、関数、クラス)を保存するためのネーミングスペースを提供しています.1つのmoduleオブジェクトが持つネーミングスペースに保存されているコンテンツはdir関数などの方法でアクセスできます
import xxx
dir(xxx)

モジュールとパッケージ
pythonプロジェクトでは、実行時にメモリのモジュールオブジェクトがpyファイルに対応し、パッケージオブジェクトがpythonプロジェクトの1つに対応しています.init __. pyファイルのフォルダ.
モジュールオブジェクトでもパッケージオブジェクトでも、pythonの下部にPyModuleObjectオブジェクトを一括して保存します.
    >>>import os # 
    >>> import os.path #   
    >>> type(os)
        
    >>> type(os.path)
        

ご存じのように、pyファイルに対応するモジュールでは、最初のimportの過程で論理が実行されます.
これに対応して、1つのパケットは、最初のimportの過程で、パケット中__を実行します.init __.pyの論理.
importの内幕
python実装では、importメカニズムのコア実装がimport_にあります.module_レベル関数です.
static PyObject *import_module_level(char *name, PyObject *globals, PyObject *locals,PyObject *fromlist, int level)
{
    //     import        
    parent = get_parent(globals, buf, &buflen, level);
    ...
    
    //   import    ,        
    head = load_next(parent, level < 0 ? Py_None : parent, &name, buf,&buflen);
    tail = head;
    while (name) {
        next = load_next(tail, tail, &name, buf, &buflen);
        tail = next;
    }
    
    ...
}

import文実行環境の取得
import_module_level関数では、最初のコアの操作はimport文の実行環境を取得することです.この操作はget_parent関数で得られます.importの実行環境とは,現在のimport文が存在するモジュールが属するpackageである.
たとえば、test_というパッケージが現在あるとします.pkg、pyファイルtest_を含むmod.py.test_mod.pyにはimport文が含まれています.
# test_mod.py

import x.y.z

仮想マシンがimport文を実行し、import_をトリガーするとmodule_level関数呼び出し後、get_parent操作が返す実行環境、すなわちtest_pkg.前述したようにtest_pkgはこのときもPyModuleObjectによって記述される.
下に続けてget_を探るparentは,この関数の大部分が現在のモジュールの__を解析することによって発見される.name __ 属性は上位packageの名前を取得し、グローバルsys.modulesはpackageオブジェクトにクエリーします.
通常test_mod.py対応モジュールその_name __ 属性の値は「test_pkg.test_mod」です.最後の「.」記号の位置によって、上位package名を取得できます.
特別な状況はtest_modが起動モジュール(python test_mod.pyを実行)として設定されている場合test_modモジュールの_name __ 属性値は「_main_」です.このとき、その上位実行環境はPy_に設定されるNone.
importパスチェーンに沿ってパッケージとモジュールを順次ロード
実行環境を取得したら、パッケージとモジュールのロードを正式に開始します.文import x.y.zの場合.
x->y->zはチェーンテーブルと見なすことができますimport_module_level関数では、チェーンテーブルの遍歴に似た操作が行われ、ノードごとにロード操作が実行されます.
//   import    ,        
    head = load_next(parent, level < 0 ? Py_None : parent, &name, buf,&buflen);
    tail = head;
    while (name) {
        next = load_next(tail, tail, &name, buf, &buflen);
        tail = next;
    }

load_nextのコア関数はimport_submodule関数
static PyObject *import_submodule(PyObject *mod, char *subname, char *fullname){
    
    //             sys.modules  
    if ((m = PyDict_GetItemString(modules, fullname)) != NULL) {
    Py_INCREF(m); 
}

    //    mod.__path__             
    if (mod == Py_None)
        path = NULL;
    else {
        path = PyObject_GetAttrString(mod, "__path__");
        if (path == NULL) {
            PyErr_Clear();
            Py_INCREF(Py_None);
            return Py_None;
        }
    }

    //     
    fdp = find_module(fullname, subname, path, buf, MAXPATHLEN+1,
                      &fp, &loader);
   
    //     
    m = load_module(fullname, fp, buf, fdp->type, loader);
    Py_XDECREF(loader);
    
    //           package         
    if (!add_submodule(mod, m, fullname, subname, modules)) {
        Py_XDECREF(m);
        m = NULL;
    }
    
}

    return m;
}

import_submodule関数では、
まず、ターゲット名に対応するPyModuleObjectオブジェクトがsysにロードするかどうかを検出する.modules辞書にあります.
もしsys.modulesにこのモジュールオブジェクトが見つからない場合は、このモジュールがグローバルで初めてロードされたことを示します.次に、次の操作を行います.
  • 現在受信されているpackageオブジェクトの__に従ってpath __ プロパティは、ターゲットパッケージまたはモジュールの検索パスを取得します.受信packageオブジェクトがPyNoneである場合(_name_=="_main_")などの場合)、パスはNULLです.
  • pathを取得した後、python下位層はfind_を通過するmodule関数は、ターゲットパッケージまたはモジュールのファイルシステムハンドルを検索します.find_moduleのモジュール検索ポリシーは、まず受信path(すなわち親packageのパス)でターゲットを検索し、pathがNULLである場合、またはpathで検索に失敗した場合、sysの順に検索する.pathリストに表示されるパスの下で検索します.
  • load_moduleは、モジュールまたはパッケージの実際のロード動作を実行します.load_Moduleは、ターゲットPyModuleObjectオブジェクトの予想されるタイプに応じて異なる操作を行います:
  • load_module(){
        
        switch(type){
            
            case py_source:
                //      py
                1.  
                2.    
                3.   module   
                4.     global       module->m_dict  
                5.    module   
                break
            case py_complied:
                //      pyc,         
                1.    
                2.   module   
                3.     global       module->m_dict  
                4.    module   
                break
            case c_extension:
                //      c   
                1.        
                2.   initmodule   ,     Py_InitModule api
                break
            case pkg:
                //       package
                1.    
                2.  __init__.py,  load_module(__init__.py).
                break
            }
        }
    
  • add_submodule関数は、親PyModuleObjectにloadが入力したターゲットPyModuleObjectを挿入します.