web-dev-qa-db-ja.com

相対インポートでトップレベルパッケージを超えるエラー

Python 3での相対インポートについては、すでにかなりの質問があるようですが、それらの多くを調べた後も、私はまだ自分の問題に対する答えを見つけることができませんでした。だからここに問題があります。

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

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

そして、私はtest.pyに一行あります。

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は1レベルだけ上がります、それはまだpackageフォルダの中にあります、なぜそれはそれがbeyond top-level packageを言うメッセージを与えるか。このエラーメッセージの原因は何ですか?

184
shelper

編集:他の質問でこの質問へのより良い/より首尾一貫した答えがあります:


なぜ動かないのですか?パッケージがどこからロードされたのかpythonが記録していないためです。そのため、python -m test_A.testを実行すると、test_A.testが実際にpackageに格納されているという知識は基本的に破棄されます(つまり、packageはパッケージとは見なされません)。 from ..A import fooを試みると、それがもう持っていない情報(すなわち、ロードされた場所の兄弟ディレクトリ)にアクセスしようとしています。概念的には、mathのファイルでfrom ..os import pathを許可することに似ています。あなたはパッケージを区別したいのでこれは悪いでしょう。他のパッケージから何かを使用する必要がある場合は、from os import pathを使用してそれらをグローバルに参照し、それが$PATHおよび$PYTHONPATHを使用する場所でpythonを使用できるようにします。

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

なぜpythonは現在の作業ディレクトリをパッケージと見なさないのですか?いいえCLUE、しかし便利だと思います。

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

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

90
jenish Sakhiya

仮定:
packageディレクトリにいる場合、Atest_Aは別々のパッケージです。

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

その他の注意事項
相対インポートをパッケージ内でのみ利用可能にすることは、パッケージをsys.pathにある任意のパスに配置できるようにする場合に便利です。

編集:

これが異常であると思うのは私だけです。なぜ現在の作業ディレクトリがパッケージと見なされないのですか? - Multihunter

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

34
User

from package.A import foo

私はそれがよりはっきりしていると思う

import sys
sys.path.append("..")
9
Joe Zhow

これらの解決策のどれも3.6のようなフォルダ構造で私にはうまくいきませんでした:

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

私の目標はmodule1からmodule2にインポートすることでした。ついに私のために働いたのは、おかしなことに、十分でした:

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

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


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

import os
print (os.getcwd())

私の場合、作業ディレクトリは(予想外にも)プロジェクトのルートでした。

9
Jason DeMorrow

誰かがまだ提供されているすばらしい答えの後にまだ少し苦労しているならば、これをチェックすることを考えてください:

https://www.daveoncode.com/2017/03/07/how-to-solve-python-modulenotfound-no-module-named-import-error/

上記のサイトからの本質的な引用:

msgstr "同じようにプログラム的に同じことを指定できます:

インポートシステム

sys.path.append( '..')

もちろん、上のコードは他のimportステートメントの前に書かなければなりません。

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

5
Mierpo

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

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

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

次の理由で、この方法で呼び出されたときにPythonパスを強制します。

python -m test_A.testを使ってtest_A/test.py__== '__main__'を使って__file__ == '/absolute/path/to/test_A/test.py'を実行しています

つまり、test.pyでは、あなたは絶対的なimportをメインケースの状態で半保護して使用することができ、また一回限りのPythonパス操作を行うことができます。

from os import path
…
def main():
…
if __== '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())
4
dlamblin

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

import alias
2
pelos