説明
PEP 328から
相対インポートは、モジュールの__name__属性を使用して、パッケージ階層におけるそのモジュールの位置を決定します。モジュールの名前にパッケージ情報が含まれていない場合(たとえば、「__ main__」に設定されている場合)
、相対インポートは、モジュールがファイルシステムの実際の場所に関係なく、最上位モジュールであるかのように解決されます。
ある時点で、PEP 338はPEP 328と競合しました。
...相対インポートは__name__に依存して、パッケージ階層における現在のモジュールの位置を決定します。メインモジュールでは、__name__の値は常に'__main__'であるため、明示的な相対インポートは常に失敗します(パッケージ内のモジュールに対してのみ機能するため)
この問題に対処するために、PEP 366はトップレベルの変数を導入しました__package__
:
新しいPEPは、新しいモジュールレベルの属性を追加することにより、-m
スイッチを使用してモジュールを実行した場合に、相対的なインポートが自動的に機能するようにします。モジュール自体に少量のボイラープレートがあれば、ファイルが名前で実行されたときに相対インポートが機能します。[...] [属性]が存在する場合、相対インポートはモジュールの__name__属性ではなく、この属性に基づいて行われます。[...]メインモジュールがファイル名で指定されている場合、__package__属性はNoneに設定されます。[...] インポートシステムは、__ package__が設定されていない(またはNoneに設定されている)モジュールで明示的な相対インポートを検出すると、正しい値(__name __。rpartition( '')通常のモジュールの[0]と__name__パッケージの初期化モジュール用)
(強調鉱山)
場合__name__
で'__main__'
、__name__.rpartition('.')[0]
空の文字列を返します。これが、エラーの説明に空の文字列リテラルがある理由です。
SystemError: Parent module '' not loaded, cannot perform relative import
CPythonのPyImport_ImportModuleLevelObject
関数の関連部分:
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
(Pythonでアクセス可能package
)でinterp->modules
(パッケージの名前)が見つからなかった場合、CPythonはこの例外を発生させますsys.modules
。sys.modules
は「モジュール名を既にロードされているモジュールにマップするディクショナリ」であるため、相対インポートを実行する前に、親モジュールを明示的に絶対インポートする必要があることは明らかです。
注:問題18018 のパッチにより、上記のコードの前に実行される別のif
ブロックが追加されました。
if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
...
*/
場合package
、空の文字列である(同上)、エラーメッセージがあろう
ImportError: attempted relative import with no known parent package
ただし、これはPython 3.6以降でのみ表示されます。
解決策1:-mを使用してスクリプトを実行する
ディレクトリ(Python パッケージ)を考えてみます。
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
パッケージ内のすべてのファイルは、同じ2行のコードで始まります。
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
私はこれらの2行を含むてるだけの操作の順序を明らかにします。それらは実行に影響を与えないので、それらを完全に無視することができます。
__init__.pyおよびmodule.pyには、これらの2行のみが含まれます(つまり、実質的に空です)。
standalone.pyは、相対インポートを介してmodule.pyをインポートしようとします:
from . import module # explicit relative import
/path/to/python/interpreter package/standalone.py
失敗することは承知しております。ただし、「指定されたモジュールを検索し、その内容をモジュールとして実行する」-m
コマンドラインオプションを使用してモジュールを実行できます。sys.path
__main__
vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
-m
すべてのインポート処理を自動的に行い、自動的に設定__package__
しますが、
解決策2:__package__を手動で設定する
実際のソリューションではなく、概念実証として扱ってください。実際のコードでの使用には適していません。
PEP 366にはこの問題の回避策がありますが、設定__package__
だけでは十分ではないため、不完全です。モジュール階層で少なくともN個の先行パッケージをインポートする必要があります。Nは、インポートされるモジュールを検索する(スクリプトのディレクトリを基準にした)親ディレクトリの数です。
したがって、
現在のモジュールのN番目の先行ノードの親ディレクトリを追加しますsys.path
現在のファイルのディレクトリを削除します sys.path
完全修飾名を使用して現在のモジュールの親モジュールをインポートします
2__package__
からの完全修飾名に設定
相対インポートを実行する
ソリューション#1からファイルを借りて、さらにいくつかのサブパッケージを追加します。
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
今回、standalone.pyは、以下の相対インポートを使用して、パッケージ package からmodule.pyをインポートします
from ... import module # N = 3
これを機能させるには、その行の前にボイラープレートコードを配置する必要があります。
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[3]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
ファイル名でstandalone.pyを実行できます:
vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
関数にラップされたより一般的な解決策はここにあります。使用例:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
手順は-
明示的な相対インポートを同等の絶対インポートに置き換えます
インストールpackage
してインポート可能にする
たとえば、ディレクトリ構造は次のようになります。
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
ここで、setup.pyは
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
残りのファイルはSolution#1から借用したものです。
インストールにより、作業ディレクトリに関係なくパッケージをインポートできます(名前付けの問題がない場合)。
この利点を使用するようにstandalone.pyを変更できます(ステップ1)。
from package import module # absolute import
作業ディレクトリをに変更してproject
実行します/path/to/python/interpreter setup.py install --user
(--user
パッケージをサイトパッケージディレクトリにインストールします)(ステップ2):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
スクリプトとしてstandalone.pyを実行できるようになったことを確認します。
vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
注:この方法を使用する場合は、仮想環境を使用してパッケージを個別にインストールすることをお勧めします。
解決策4:絶対インポートと定型コードを使用する
率直に言って、インストールは必要ありません。スクリプトに定型コードを追加して、絶対インポートを機能させることができます。
ソリューション#1からファイルを借りて、standalone.pyを変更します。
絶対インポートを使用してパッケージから何かをインポートする前に、パッケージの親ディレクトリを追加します。sys.path
import sys
from pathlib import Path # if you haven't already done so
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# Additionally remove the current file's directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
相対インポートを絶対インポートに置き換えます。
from package import module # absolute import
standalone.pyは問題なく実行されます:
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
私はあなたに警告すべきだと思います:特にプロジェクトが複雑な構造を持っている場合は、これを行わないようにしてください。
補足として、PEP 8は絶対インポートの使用を推奨していますが、一部のシナリオでは明示的な相対インポートが許容されると述べています。
絶対インポートは、通常は読みやすく、動作が良好になる傾向がある(または少なくともエラーメッセージが改善される)ため、推奨されます。[...]ただし、明示的な相対インポートは、特に絶対インポートの使用が不必要に冗長になる複雑なパッケージレイアウトを処理する場合、絶対インポートの許容可能な代替手段です。