相対インポートの最上位パッケージエラーを超えて


317

python 3での相対インポートについてはすでにかなりの質問があるようですが、それらの多くを試しても、問題の答えがまだ見つかりませんでした。ここに質問があります。

以下に示すパッケージがあります

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

そして私はtest.pyに1行あります:

from ..A import foo

今、私はのフォルダにいてpackage、実行します

python -m test_A.test

メッセージをもらいました

"ValueError: attempted relative import beyond top-level package"

しかし、私がの親フォルダにいるpackage場合、たとえば次のように実行します。

cd ..
python -m package.test_A.test

すべて順調。

私の質問は 、私がのフォルダーにいるときにpackage、test_Aサブパッケージ内のモジュールをとして実行するtest_A.testと、私の理解に基づいて、..Aまだpackageフォルダー内にある1レベルだけ上がるため、メッセージにと表示されますbeyond top-level package。このエラーメッセージの原因は何ですか?


49
その投稿は私の「最上位パッケージを超えた」エラーを説明していませんでした
shelper

4
私はここで考えているので、test_A.testをモジュールとして実行すると、 '..'はtest_Aの上に移動します。test_Aは、すでにインポートtest_A.testの最高レベルです。パッケージレベルはディレクトリレベルではなく、パッケージをインポートするレベル。
ヘルパー

2
この回答を読んだ後、相対的なインポートに関するすべてを理解することを約束しますstackoverflow.com/a/14132912/8682868
pzjzeason

この問題の詳細な説明については、ValueError:トップレベルのパッケージ超えて相対インポートを試みたを参照してください。
napuzba

相対インポートを回避する方法はありますか?EclipseのPyDevが<PydevProject> / src内のすべてのパッケージを見る方法など。
Mushu909

回答:


173

編集:他の質問には、この質問に対するより良い/より一貫した答えがあります:


なぜ機能しないのですか?これは、Pythonがパッケージのロード元を記録しないためです。したがって、実行するとpython -m test_A.test、基本的にtest_A.testは実際に格納されているpackage(つまりpackage、パッケージとは見なされない)ナレッジが破棄されるだけです。しようとは、from ..A import fooこれ以上(ロードされた場所のつまり兄弟ディレクトリ)を持っていないアクセス情報にしようとしています。概念的にfrom ..os import pathは、のファイルでの許可と似ていますmath。パッケージを個別にしたいので、これは悪いことです。彼らが別のパッケージの何かを使用する必要がある場合、彼らはそれらをグローバルに参照しfrom os import path、Pythonがそれがどこにあるかを理解させる必要が$PATHあり$PYTHONPATHます。

を使用するとpython -m package.test_A.testfrom ..A import foo何が入っているかを追跡しpackage、ロードされた場所の子ディレクトリにアクセスしているだけなので、を使用すると問題なく解決されます。

Pythonが現在の作業ディレクトリをパッケージと見なさないのはなぜですか? NO CLUEは、しかし、おやっそれは有用であろう。


2
私は同じことになる質問に対するより良い回答を参照するように私の回答を編集しました。回避策しかありません。私が実際に見た唯一のことは、-mフラグを使用して上記のディレクトリから実行することです。
Multihunter 2018

1
ことに留意すべきである、この答えは、マルチハンターによって与えられたリンクから、関与しないsys.pathハックが、使用setuptoolsの私の意見でははるかに興味深いです。
Angelo Cardellicchio

157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

これを試して。私のために働いた。


10
うーん...この作品はどうですか?すべての単一のテストファイルにこれがありますか?
ジョージマウアー

ここでの問題は、たとえば、A/bar.py存在し、foo.pyあなたの中に存在する場合ですfrom .bar import X
user1834164 2018

9
sys.path.append( "..")を追加した後、「。..A import ...」から..を削除する必要がありました
Jake OPJ

2
スクリプトが存在するディレクトリの外部から実行された場合、これは機能しません。代わりに、この回答を微調整して、上記のスクリプトの絶対パス指定する必要があります
Manavalan Gajapathy

これは最良で最も複雑でないオプションです
Alex R

43

仮定:
あなたがしている場合はpackage、ディレクトリ、Aおよびtest_A独立したパッケージです。

結論:
..Aインポートはパッケージ内でのみ許可されます。

詳細注記:
相対インポートをパッケージ内でのみ利用できるようにすると、パッケージをにある任意のパスに配置できるようにする場合に役立ちますsys.path

編集:

これってめちゃくちゃだと思ってるのは私だけ!?なぜ現在の作業ディレクトリはパッケージと見なされないのですか?– マルチハンター

現在の作業ディレクトリは通常、sys.pathにあります。したがって、そこにあるすべてのファイルがインポート可能です。これは、パッケージがまだ存在しないPython 2以降の動作です。実行中のディレクトリをパッケージにすると、「インポート.A」および「インポートA」としてモジュールをインポートできるようになり、2つの異なるモジュールになります。多分これは検討すべき矛盾です。


86
これってめちゃくちゃだと思ってるのは私だけ!?なぜ実行中のディレクトリがパッケージと見なされないのですか?
Multihunter 2017年

13
これは正気ではないだけでなく、役に立ちません...では、どうやってテストを実行しますか?明らかに、OPが求めていたことと、多くの人がここにもいると確信している理由。
George Mauer

実行中のディレクトリは通常、sys.pathにあります。したがって、そこにあるすべてのファイルがインポート可能です。これは、パッケージがまだ存在しないPython 2以降の動作です。-回答を編集しました。
ユーザー

私は矛盾を追跡しません。の動作はpython -m package.test_A.test必要なことを行うようですが、私の主張では、これがデフォルトである必要があります。では、この矛盾の例を挙げていただけますか?
マルチハンター、

私は実際に考えています、これに対する機能要求はありますか?これは確かに非常識です。C / C ++スタイル#includeは非常に便利です。
ニコラスハンフリー

29

これらの解決策はどれも3.6では機能せず、次のようなフォルダー構造でした。

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

私の目標は、module1からmodule2にインポートすることでした。ついに私にとってうまくいったのは、奇妙なことに、

import sys
sys.path.append(".")

これまでに説明した2ドットのソリューションとは対照的に、1つのドットに注意してください。


編集:以下は私にとってこれを明確にするのに役立ちました:

import os
print (os.getcwd())

私の場合、作業ディレクトリは(予期せず)プロジェクトのルートでした。


2
ローカルで動作していますが、aws ec2インスタンスでは動作していませんが、意味がありますか?
thebeancounter

これは私にとってもうまくいきました-私の場合、作業ディレクトリは同様にプロジェクトのルートでした。私はプログラミングエディター(TextMate)から実行ショートカットを使用していました
JeremyDouglass

@thebeancounter同じです!Macでローカルに動作しますが、ec2では動作しません。ec2のサブディレクトリでコマンドを実行し、ローカルでrootで実行していることに気付きました。ec2のrootから実行すると、うまくいきました。
Logan Yang、

これは私にとってもうまくいきました。そのsysメソッドから、「..」を必要とせずにパッケージを簡単に呼び出すことができます
RamWill

sys.path.append(".")あなたがいることをノート、親ディレクトリでそれを呼び出しているので、働いていた.。いつもあなたがPythonのコマンドを実行したディレクトリを表す
KevinZhou

13

from package.A import foo

より明確だと思います

import sys
sys.path.append("..")

4
確かに読みやすいですが、それでも必要sys.path.append("..")です。Python 3.6でテスト済み
MFA

以前の回答と同じ
nrofis

12

最も人気のある答えが示唆するように、基本的にあなたのためPYTHONPATHかはsys.path含んで.あなたのパッケージへのパスではなく。また、相対インポートは、インポートが発生するファイルではなく、現在の作業ディレクトリを基準にしています。奇妙に。

これを修正するには、まず相対インポートを絶対に変更してから、次のいずれかで開始します。

PYTHONPATH=/path/to/package python -m test_A.test

または、次の理由により、このように呼び出されたときにpythonパスを強制します。

python -m test_A.testあなたが実行しているtest_A/test.py__name__ == '__main__'し、__file__ == '/absolute/path/to/test_A/test.py'

つまり、メインのケース条件でtest.py絶対的なimportセミプロテクトを使用し、Pythonパスの操作を一度だけ行うことができます。

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

8

編集:2020-05-08:引用したWebサイトは、アドバイスを書いた人によって制御されなくなったようです。そのため、サイトへのリンクを削除します。baxxについてお知らせいただきありがとうございます。


すでにすばらしい回答が提供されているにもかかわらず誰かがまだ少し苦労している場合、私はWebサイトで利用できなくなったアドバイスを見つけました。

私が言及したサイトからの本質的な引用:

「同じ方法でプログラムで指定できます。

インポートシステム

sys.path.append( '..')

もちろん、上記のコードは他のインポート 文の前に書く必要があります

それがこのようにならなければならないことは明らかであり、事後に考えます。テストでsys.path.append( '..')を使用しようとしましたが、OPによって投稿された問題に遭遇しました。他のインポートの前にインポートとsys.path定義を追加することで、問題を解決することができました。


投稿したリンクは無効です。
baxx

知らせてくれてありがとうございます。ドメイン名は同じ人物によって制御されなくなったようです。リンクを削除しました。
ミエルポ

5

あなたが持っている場合は__init__.py、上のフォルダに、あなたはとしてインポートを初期化することができ import file/path as alias、そのinitファイルインチ 次に、それを下位スクリプトで次のように使用できます。

import alias

0

私の控えめな意見では、私はこの質問をこのように理解しています:

[ケース1]次のような絶対インポートを開始すると

python -m test_A.test

または

import test_A.test

または

from test_A import test

実際にインポートアンカーをに設定しています。つまりtest_A、最上位パッケージはtest_Aです。したがって、test.py do from ..A import xxxを実行すると、アンカーから脱出していることになります。Python ではこれを許可していません。

[ケース2]行うとき

python -m package.test_A.test

または

from package.test_A import test

あなたのアンカーはなりpackageそう、package/test_A/test.pyやってfrom ..A import xxx(まだ内部にアンカーをエスケープしていないpackageフォルダ)、およびPythonは喜んでこれを受け入れます。

要するに:

  • Absolute-importは現在のアンカーを変更します(=トップレベルのパッケージを再定義します)。
  • 相対インポートはアンカーを変更しませんが、それに限定します。

さらに、完全修飾モジュール名(FQMN)を使用して、この問題を検査できます。

いずれの場合もFQMNを確認します。

  • [ケース2] test.__name__=package.test_A.test
  • [ケース1] test.__name__=test_A.test

したがって、CASE2の場合、from .. import xxxaはFQMN =の新しいモジュールになりますがpackage.xxx、これは許容範囲です。

CASE1の場合、..from from .. import xxxはの開始ノード(アンカー)からジャンプしますがtest_A、これはPythonでは許可されていません。


2
これは必要以上に複雑です。PythonのZenについてはこれで終わりです。
AtilioA

0

Python 2.xではわかりませんが、Python 3.6では、スイート全体を実行しようとしていると仮定すると、使用する必要があります -t

-t、--top-level-directory directoryプロジェクトの最上位ディレクトリ(デフォルトは開始ディレクトリ)

したがって、次のような構造で

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

たとえば、次のように使用できます。

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

そして、まだmy_module.my_class主要なドラマなしでインポートします。

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