Python 3での相対インポート


714

同じディレクトリ内の別のファイルから関数をインポートしたい。

時々それは私のためにうまくfrom .mymodule import myfunctionいきますが、時々私は次のものを得ます:

SystemError: Parent module '' not loaded, cannot perform relative import

時々それはで動作しますfrom mymodule import myfunctionが、時々私はまた得ます:

SystemError: Parent module '' not loaded, cannot perform relative import

ここでは論理が理解できず、説明もありませんでした。これは完全にランダムに見えます。

誰かがこのすべての背後にある論理は何であるかを私に説明できますか?


76
これは、パッケージ内のモジュールをスクリプトとして実行していることを意味します。パッケージの外部からのみスクリプトを実行します。
Martijn Pieters

3
おそらく、あなたが言及する「時々」の条件を定義する必要があります。ランダムなエラーがあるわけではないことを理解しています。
joaquin 2013年

15
@MartijnPieters:まあ、残念ながら、このモジュールはパッケージ内にある必要があり、スクリプトとして実行できる必要がある場合もあります。どうすればそれを達成できるのでしょうか?
ジョン・スミスオプション

22
@JohnSmithオプション:パッケージ内でのスクリプトの混合はトリッキーであり、可能な限り回避する必要があります。代わりに、パッケージをインポートして 'scripty'関数を実行するラッパースクリプトを使用してください。
Martijn Pieters

3
残念です。特定の種類のファイルを解析/分析できるクラス/メソッドでコアモジュールを作成し、それをインポートする別のセカンダリモジュールとスクリプトも(主に私自身が)持っています-これらはこれらのファイルをマッサージ/変換できます。ただし、エンドユーザーにその単一のコアファイル(複雑なパッケージ全体ではない)を渡して、ファイルの横に簡単に配置して実行できるようにしたいとも思います。その「スクリプトモード」では、ファイルとエンコーディングを解析および分析し、さまざまなフィールド/値/特殊文字を集計し、レポートを提供します。ただし、実際にはファイルは変更されません。アンチパターン?
Jon Coombs 2014年

回答:


528

残念ながら、このモジュールはパッケージ内にある必要があり、スクリプトとして実行できる必要がある場合もあります。どうすればそれを達成できるのでしょうか?

このようなレイアウトを持つことはかなり一般的です...

main.py
mypackage/
    __init__.py
    mymodule.py
    myothermodule.py

... mymodule.pyこんな感じで...

#!/usr/bin/env python3

# Exported function
def as_int(a):
    return int(a)

# Test function for module  
def _test():
    assert as_int('1') == 1

if __name__ == '__main__':
    _test()

... myothermodule.pyこのような...

#!/usr/bin/env python3

from .mymodule import as_int

# Exported function
def add(a, b):
    return as_int(a) + as_int(b)

# Test function for module  
def _test():
    assert add('1', '1') == 2

if __name__ == '__main__':
    _test()

...そしてmain.pyこのような...

#!/usr/bin/env python3

from mypackage.myothermodule import add

def main():
    print(add('1', '1'))

if __name__ == '__main__':
    main()

... main.pyまたはを実行すると正常に動作しますが、相対的なインポートのためにでmypackage/mymodule.py失敗しmypackage/myothermodule.pyます...

from .mymodule import as_int

あなたがそれを実行することになっている方法は...

python3 -m mypackage.myothermodule

...しかし、それは幾分冗長で、のようなシバンラインとうまく混ざりません#!/usr/bin/env python3

このケースの最も簡単な修正は、名前mymoduleがグローバルに一意であると想定し、相対インポートの使用を避け、単に使用することです...

from mymodule import as_int

...ただし、一意ではない場合、またはパッケージ構造がより複雑な場合は、パッケージディレクトリを含むディレクトリをに含めて、次のようにする必要がありますPYTHONPATH...

from mypackage.mymodule import as_int

...または「そのまま」動作するようにしたい場合は、PYTHONPATH最初に次のコードを入力することができます...

import sys
import os

PACKAGE_PARENT = '..'
SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))

from mypackage.mymodule import as_int

ちょっと苦痛ですが、特定のグイドファンロッサムが書いたメールに理由があるという手がかりがあります...

これと、提案されているその他の__main__ 機械のいじりについては、-1です。唯一の使用例は、モジュールのディレクトリ内にたまたま存在するスクリプトを実行しているようです。これは、常にアンチパターンと見なされてきました。私の考えを変えさせるには、そうではないことを私に納得させる必要があります。

私は表示するには、ソースファイルのいずれかのスクリプトを実行できるようにパッケージ内のスクリプトを実行しているかどうか、アンチパターンがあるか、ない主観ですが、個人的に私はいくつかのカスタムwxPythonのウィジェットが含まれている私が持っているパッケージで、それは本当に便利wx.Frameだけ含有しましたテスト用のウィジェット。


7
SCRIPTDIRを取得するためのより良い方法は、で与えられる相対パスからインポートモジュールのコメントとしてos.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe())))、あなたのモジュールは常に持っている適切なことを、あなたの自信を持っている場合file、あなたにも使用することができますos.path.realpath(os.path.dirname(__file__))
marcz 14年

2
あなたはより短く、読みやすいコードスニペットを適用することによって、あなたのPYTHONPATHを展開することができます sys.path.append( os.path.join( os.path.dirname(__file__), os.path.pardir ) )
アレックス・ボグダノフ

12
...which I've always seen as an antipattern.アンチパターンであることがわかりません...相対インポートを直感的に機能させることは非常に便利だと思われます。同じディレクトリにあることがわかっているものをインポートできるようにしたいだけです。彼の推論は何だったのだろう
ヨンガン

9
グイドは再びストライキをしました。まあそれはもう起こりません。
javadba

4
これは、Pythonについて今まで見た中で最も悲しいことです。
AtilioA

263

説明

PEP 328から

相対インポートは、モジュールの__name__属性を使用して、パッケージ階層におけるそのモジュールの位置を決定します。モジュールの名前にパッケージ情報が含まれていない場合(たとえば、「__ main__」に設定されている場合、相対インポートは、モジュールがファイルシステムの実際の場所に関係なく、最上位モジュールあるかのように解決されます。

ある時点で、PEP 338PEP 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.modulessys.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は、インポートされるモジュールを検索する(スクリプトのディレクトリを基準にした)親ディレクトリの数です。

したがって、

  1. 現在のモジュールのN番目の先行ノードの親ディレクトリを追加しますsys.path

  2. 現在のファイルのディレクトリを削除します sys.path

  3. 完全修飾名を使用して現在のモジュールの親モジュールをインポートします

  4. 2__package__からの完全修飾名に設定

  5. 相対インポートを実行する

ソリューション#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

解決策3:絶対インポートとsetuptoolsを使用する

手順は-

  1. 明示的な相対インポートを同等の絶対インポートに置き換えます

  2. インストール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を変更します。

  1. 絶対インポートを使用してパッケージから何かをインポートする前にパッケージの親ディレクトリを追加します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
  2. 相対インポートを絶対インポートに置き換えます。

    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は絶対インポートの使用を推奨していますが、一部のシナリオでは明示的な相対インポートが許容されると述べています。

絶対インポートは、通常は読みやすく、動作が良好になる傾向がある(または少なくともエラーメッセージが改善される)ため、推奨されます。[...]ただし、明示的な相対インポートは、特に絶対インポートの使用が不必要に冗長になる複雑なパッケージレイアウトを処理する場合、絶対インポートの許容可能な代替手段です。


3
__package__名前が__main__問題を解決するためのものである場合、手動で設定することは可能ですか?
Paulo Scardine、2015年

ありがとう、いい答え!モジュールを使用してimpモジュールをロードし、__package__それに応じて設定することができましたが、結果は明らかにアンチパターンです。
Paulo Scardine、2015年

エラーが発生しますAttributeError: 'PosixPath' object has no attribute 'path'
ユーザーの

迅速な返信ありがとうございます。nltkパッケージを使用していますが、エラーが発生します。`File "/usr/local/lib/python3.5/dist-packages/nltk/__init__.py"、line 115、in <module> in nltk.decoratorsデコレーターのインポート、メモ化ファイル "/usr/local/lib/python3.5/dist-packages/nltk/decorators.py"、23行目、<module> sys.path = [p for sys.path in" nltk "not in p]ファイル" /usr/local/lib/python3.5/dist-packages/nltk/decorators.py "、line 23、in <listcomp> sys.path = [p for p in sys.path if" nltk "not in p] TypeError:タイプ 'PosixPath'の引数は反復可能ではありません`
ユーザー

1
また、(相対すぎ)ファイルパスでファイルをインポートすることができますdocs.python.org/3/library/...
Ctrl-Cを

86

これをパッケージの__init__.pyファイル内に配置します

# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

あなたのパッケージが次のようであると仮定します:

├── project
   ├── package
      ├── __init__.py
      ├── module1.py
      └── module2.py
   └── setup.py

次のように、パッケージで通常のインポートを使用します。

# in module2.py
from module1 import class1

これはpython 2と3の両方で機能します。


1
weelとしてパッケージ化した場合、これは機能します
Alex Punnen

1
また、これはもっと投票に値すると思います。これをすべてに配置__init__.pyすると、基本的にすべての相対的なインポートエラーが解決されます。
frankliuao

3
他の人のために話すことはできませんが、sys.path他のコードに影響するのではないかと心配しているので、変更を避ける傾向があります。(これは部分的には、それがどのように機能するかの複雑さを知らないためです。)
pianoJames

@pianoJames私はあなたが何を意味するのか知っています、これは(一見、多くのねじ込みの後の)魔法の修正は少し簡単すぎるようです。しかし、それは機能します。これが否定的な副作用を持っているかどうか、それらを知っている人から知らないことに興味があるでしょう。
Jon

私はこれを今使っています:これまでのところとても良いです。
javadba

37

この問題に遭遇しました。ハック回避策は、次のようにif / elseブロックを介してインポートすることです。

#!/usr/bin/env python3
#myothermodule

if __name__ == '__main__':
    from mymodule import as_int
else:
    from .mymodule import as_int


# Exported function
def add(a, b):
    return as_int(a) + as_int(b)

# Test function for module  
def _test():
    assert add('1', '1') == 2

if __name__ == '__main__':
    _test()

29
それはとても良い解決策ではありません。また、裸except:は悪いです。except ImportError:代わりに使用してください!
ThiefMaster 2015年

6
それはSystemErrorここに。(Py 3.4)
Avi 2015年

8
これはひどい考えではありませんが、try / exceptではなく、どのインポートを使用するかを検出する方が良いでしょう。のようなものif __name__ == '__main__': from mymod import as_int; else: from .mymod import as_int
パーキンス

@パーキンスまあ...ほとんどの場合それはしません。ただし、相対的な輸入は例外かもしれません。
wizzwizz4

8

うまくいけば、これはそこにいる誰かにとって価値があるでしょう-私はここに上に投稿されたものと同様の相対的なインポートを理解しようとする6ダースのstackoverflowの投稿を経験しました。提案どおりにすべてを設定しましたが、まだヒットしていましたModuleNotFoundError: No module named 'my_module_name'

ローカルで開発して遊んでいるだけなので、setup.pyファイルを作成/実行していませんでした。どうやら私の設定もしていなかったPYTHONPATH

テストがモジュールと同じディレクトリにあるときと同じようにコードを実行すると、モジュールが見つからないことに気付きました。

$ python3 test/my_module/module_test.py                                                                                                               2.4.0
Traceback (most recent call last):
  File "test/my_module/module_test.py", line 6, in <module>
    from my_module.module import *
ModuleNotFoundError: No module named 'my_module'

しかし、パスを明示的に指定すると、物事が機能し始めました:

$ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s

OK

したがって、誰かがいくつかの提案を試みた場合、それらのコードは正しく構造化されていると信じていますが、現在のディレクトリをPYTHONPATHにエクスポートしない場合、私は次のいずれかを試してみてください。

  1. コードを実行し、次のようにパスを明示的に含めます。 $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. の呼び出しを回避PYTHONPATH=.するにはsetup.py、次のような内容のファイルを作成し、実行python setup.py developmentしてパスにパッケージを追加します。
# setup.py
from setuptools import setup, find_packages

setup(
    name='sample',
    packages=find_packages()
)

6

それを機能させるには、メインプロジェクトディレクトリからpython3を実行する必要がありました。

たとえば、プロジェクトの構造が次の場合:

project_demo/
├── main.py
├── some_package/
   ├── __init__.py
   └── project_configs.py
└── test/
    └── test_project_configs.py

解決

project_demo /フォルダー内でpython3を実行してから、

from some_package import project_configs

4

この問題を回避するために、私はしばらくの間私のために働いていたパッケージパッケージを使用してソリューションを考案しました。上位ディレクトリをlibパスに追加します。

import repackage
repackage.up()
from mypackage.mymodule import myfunction

リパッケージは、インテリジェントな戦略(コールスタックの検査)を使用して、幅広いケースで機能する相対インポートを作成できます。


はるかに簡単なソリューションです!ありがとうございました!
CodingInCircles

1
ありがとう!最良の答えを出す代わりに、私は有効な答えを出そうとしました:-)
fralau

3

両方のパッケージがインポートパス(sys.path)にあり、必要なモジュール/クラスがexample / example.pyにある場合、相対インポートせずにクラスにアクセスします。

from example.example import fkt

1

最善の解決策は、モジュールのパッケージを作成することだと思います: ここにそれを行う方法についての詳細情報です。

相対インポートを気にする必要のないパッケージを取得したら、絶対インポートを実行できます。


0

同様の問題がありました。Linuxサービスと、共通の定数を使用して連携するcgiプラグインが必要でした。これを行う「自然な」方法は、それらをパッケージのinit .pyに配置することですが、-mパラメーターでcgiプラグインを開始できません。

私の最終的な解決策は上記の解決策2に似ていました:

import sys
import pathlib as p
import importlib

pp = p.Path(sys.argv[0])
pack = pp.resolve().parent

pkg = importlib.import_module('__init__', package=str(pack))

欠点は、定数(または一般的な関数)の前にpkgを付ける必要があることです。

print(pkg.Glob)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.