1247

私はここにいました:

私がコピーしなかった多くの URL は、SO にあるものもあれば、他のサイトにあるものもありました。

永遠に繰り返される質問は次のとおりです。この「パッケージ以外で相対インポートを試みました」というメッセージを解決するにはどうすればよいですか?

ImportError: attempted relative import with no known parent package

pep-0328 でパッケージの正確なレプリカを作成しました。

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

インポートはコンソールから行われました。

私は適切なモジュールで spam と Egg という名前の関数を作成しました。当然、うまくいきませんでした。答えは私がリストした 4 番目の URL にあるようですが、それはすべて私にとって卒業生です。私がアクセスした URL の 1 つで、次の応答がありました。

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

上記の応答は有望に見えますが、私にはすべて象形文字です。だから私の質問、どうすればPythonが「非パッケージで相対インポートを試みました」と返さないようにできますか? おそらく-mを含む答えがあります。

なぜPythonがそのエラーメッセージを表示するのか、「非パッケージ」とは何を意味するのか、なぜ「パッケージ」をどのように定義するのか、幼稚園児が理解できるほど簡単な言葉で正確な答えを教えてください。

4

14 に答える 14

1656

スクリプトとモジュール

ここに説明があります。簡単に言うと、Python ファイルを直接実行することと、そのファイルを別の場所からインポートすることには大きな違いがあります。 ファイルがどのディレクトリにあるかを知っているだけでは、Python がどのパッケージにあると考えるかはわかりません。 さらに、ファイルを Python にロードする方法 (実行またはインポート) にも依存します。

Python ファイルをロードするには、トップレベル スクリプトとして、またはモジュールとして 2 つの方法があります。python myfile.pyコマンド ラインに入力するなどしてファイルを直接実行すると、ファイルは最上位のスクリプトとして読み込まれます。importステートメントが他のファイル内で検出されると、モジュールとしてロードされます。一度に作成できる最上位スクリプトは 1 つだけです。最上位のスクリプトは、作業を開始するために実行した Python ファイルです。

ネーミング

ファイルがロードされると、名前が付けられます (__name__属性に保存されます)。最上位スクリプトとしてロードされた場合、その名前は__main__. モジュールとしてロードされた場合、その名前はファイル名の前に、その一部であるパッケージ/サブパッケージの名前がドットで区切られて続きます。

たとえば、あなたの例では:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

インポートした場合moduleX(注:直接実行ではなく、インポート済み)、その名前は になりますpackage.subpackage1.moduleX。をインポートした場合moduleA、その名前は になりますpackage.moduleA。ただし、コマンド ラインから直接実行する moduleXと、その名前は代わりに になり、コマンド ラインから__main__直接実行するとmoduleA、その名前は になります__main__。モジュールが最上位スクリプトとして実行されると、通常の名前が失われ、代わりに__main__.

含まれているパッケージを介さずにモジュールにアクセスする

追加のしわがあります: モジュールの名前は、それが含まれているディレクトリから「直接」インポートされたか、パッケージを介してインポートされたかによって異なります。これは、ディレクトリで Python を実行し、同じディレクトリ (またはそのサブディレクトリ) にファイルをインポートしようとした場合にのみ違いが生じます。たとえば、ディレクトリで Python インタープリターを起動してpackage/subpackage1を実行するimport moduleXと、 の名前はではなく にmoduleXなります。これは、インタプリタが対話的に入力されると、Python が現在のディレクトリを検索パスに追加するためです。インポートするモジュールが現在のディレクトリで見つかった場合、そのディレクトリがパッケージの一部であることは認識されず、パッケージ情報はモジュール名の一部になりません。moduleXpackage.subpackage1.moduleX

特殊なケースとして、インタプリタを対話的に実行する場合があります (たとえば、pythonその場で Python コードを入力して入力を開始するなど)。この場合、そのインタラクティブ セッションの名前は です__main__

エラー メッセージの重要な点は次のとおりです。モジュールの名前にドットがない場合、それはパッケージの一部とは見なされません。ファイルが実際にディスク上のどこにあるかは問題ではありません。重要なのはその名前が何であるかであり、その名前はロード方法によって異なります。

次に、質問に含めた引用を見てください。

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

相対インポート...

相対インポートでは、モジュールの名前を使用して、パッケージ内の場所を決定します。のような相対インポートを使用する場合from .. import foo、ドットはパッケージ階層のいくつかのレベルを上げることを示します。たとえば、現在のモジュールの名前がpackage.subpackage1.moduleXである場合、..moduleAを意味しpackage.moduleAます。afrom .. importが機能するには、モジュールの名前に少なくともimportステートメント内にある数と同じ数のドットが含まれている必要があります。

...パッケージ内でのみ相対的です

ただし、モジュールの名前が の場合__main__、パッケージに含まれているとは見なされません。from .. importその名前にはドットがないため、その中にステートメントを使用することはできません。そうしようとすると、「非パッケージでの相対インポート」エラーが発生します。

スクリプトは相対をインポートできません

あなたがおそらくしたことはmoduleX、コマンドラインから実行などを試みたことです。これを行ったとき、その名前は に設定されました__main__。これは、その名前がパッケージ内にあることを明らかにしないため、その中の相対インポートが失敗することを意味します。これは、モジュールがあるディレクトリから Python を実行し、そのモジュールをインポートしようとした場合にも発生することに注意してください。パッケージの一部。

また、対話型インタープリターを実行すると、その対話型セッションの「名前」は常に__main__. したがって、対話セッションから直接相対インポートを行うことはできません。相対インポートは、モジュール ファイル内でのみ使用できます。

2 つのソリューション:

  1. 本当に直接実行したいmoduleXが、それでもパッケージの一部と見なしたい場合は、python -m package.subpackage1.moduleX. は-m、最上位のスクリプトとしてではなく、モジュールとしてロードするように Python に指示します。

  2. または、実際には を実行したくなくて、たとえば内の関数を使用する他のスクリプトを実行したいだけかも しれません。その場合は、ディレクトリ内ではなく別の場所に置いて実行します。内部で次のようなことを行うと、正常に機能します。moduleXmyfile.pymoduleXmyfile.py packagemyfile.pyfrom package.moduleA import spam

ノート

  • これらのソリューションのいずれについても、パッケージ ディレクトリ (このpackage例では) は Python モジュールの検索パス ( ) からアクセスできる必要がありますsys.path。そうでない場合、パッケージ内の何も確実に使用することはできません。

  • Python 2.6 以降、パッケージ解決のためのモジュールの「名前」は、その__name__属性だけでなく、属性によっても決定され__package__ます。そのため、明示的なシンボル__name__を使用してモジュールの「名前」を参照することは避けています。Python 2.6 以降、モジュールの「名前」は事実上__package__ + '.' + __name__であるか、または単に で__name__ある場合のみ__package__ですNone。)

于 2013-01-03T04:06:37.593 に答える
5

以下は、私が推奨しない解決策の 1 つですが、モジュールが単に生成されなかった場合に役立つ可能性があります。

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()
于 2016-06-21T02:06:19.723 に答える
1

__name__問題のコードがグローバル名前空間で実行されるか、インポートされたモジュールの一部として実行されるかによって異なります。

コードがグローバル空間で実行されていない場合は__name__、モジュールの名前になります。グローバル名前空間で実行されている場合 - たとえば、コンソールに入力したり、モジュールをスクリプトとして実行したりする場合は、python.exe yourscriptnamehere.py__name__なり"__main__"ます。

コードがグローバル名前空間から実行されているかどうかをテストするために使用される多くの python コードが表示 if __name__ == '__main__'されます。これにより、スクリプトとしても機能するモジュールを使用できます。

これらのインポートをコンソールから実行しようとしましたか?

于 2013-01-03T04:01:16.457 に答える
1

Pythonモジュールの検索パスを変更したくないという同様の問題があり、スクリプトから相対的にモジュールをロードする必要がありました(BrenBarnが上でうまく説明したように、 「スクリプトはすべてに対して相対的にインポートすることはできません」にもかかわらず)。

そこで、次のハックを使用しました。imp残念ながら、バージョン 3.4 以降に非推奨になったモジュールに依存しており、 importlib. (これも で可能importlibですか?わかりません。) それでも、ハックは今のところ機能します。

フォルダーにあるスクリプトからmoduleXinのメンバーにアクセスする例:subpackage1subpackage2

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

Federico が述べたように、よりクリーンなアプローチは、モジュールのロードに使用される sys.path を変更することです。

#!/usr/bin/env python3

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *
于 2016-07-19T10:28:28.483 に答える
0

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

この質問の視聴者を助けるかもしれない小さなpythonパッケージをPyPiに書きました。インポート ファイルのディレクトリに直接移動することなく、パッケージ/プロジェクト内から上位レベルのパッケージを含むインポートを含む python ファイルを実行できるようにしたい場合、パッケージは回避策として機能します。https://pypi.org/project/import-anywhere/

于 2018-02-24T16:11:58.920 に答える