1. ホーム
  2. python

[解決済み] 億の相対的輸入

2022-03-15 02:21:07

質問事項

私はここに来たことがあります。

とコピーしなかったURLがたくさんあります。SOでも他のサイトでも、すぐに解決できると思っていた頃です。

永遠に続く疑問はこれです:この "Attempted relative import in non-package" のメッセージはどうすれば解決するのでしょうか?

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とeggsという名前の関数をそれぞれの適切なモジュールで作りました。 当然ながら、うまくいきませんでした。 答えは4番目に挙げたURLにあるらしいのですが、私にとっては全て同窓会です。私が訪れたURLの1つにこんな回答がありました。

相対インポートでは、モジュールの name 属性を使用して、パッケージ階層におけるそのモジュールの位置を決定します。もしモジュールの名前がパッケージ情報を含んでいなければ (例えば 'main' に設定されていれば)、 モジュールがファイルシステム上のどこに実際に配置されているかに関わらず、 モジュールがトップレベルモジュールであるかのように相対インポートが解決されます。

上記の回答は期待できそうですが、私には全て象形文字にしか見えません。 どうすれば Python が "Attempted relative import in non-package" を返さないようにできるかという私の質問には、おそらく -m を含む答えがあるはずです。

なぜPythonはそのようなエラーメッセージを出すのか、"non-package"の意味は何か、なぜ、どのように「パッケージ」を定義するのか、誰か教えてくれませんか? 幼稚園児でもわかるような簡単な言葉で的確な回答を .

解き方は?

スクリプトとモジュールの比較

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

Pythonファイルをロードする方法には、トップレベルのスクリプトとしてロードする方法、または モジュールになります。 ファイルを直接実行する場合、トップレベルのスクリプトとしてロードされます。 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__ .

含むパッケージを通してモジュールのNOTにアクセスする

モジュールの名前は、それがディレクトリから "直接"インポートされたか、パッケージを経由してインポートされたかによって異なります。 これは、あるディレクトリで Python を実行し、その同じディレクトリ(またはそのサブディレクトリ)にあるファイルをインポートしようとした場合にのみ、違いが生じます。 例えば、Pythonインタプリタをディレクトリ package/subpackage1 を実行します。 import moduleX の名前は moduleX は、単に moduleX であり package.subpackage1.moduleX . これは、インタプリタが対話的に入力されたときに、Python が現在のディレクトリを検索パスに追加するからです。もし、インポートするモジュールを現在のディレクトリで見つけた場合、そのディレクトリがパッケージの一部であることを知らず、パッケージ情報がモジュール名の一部とならないからです。

特殊なケースとして、インタプリタを対話的に実行した場合(例えば、単に python で、その場でPythonコードの入力を開始します)。 この場合、その対話型セッションの名前は __main__ .

さて、ここからがエラーメッセージの重要なポイントです。 モジュール名にドットがない場合、それはパッケージの一部とはみなされません。 . 実際にファイルがディスク上のどこにあるかは関係ありません。 重要なのはその名前が何であるかということであり、その名前はあなたがそれをどのように読み込んだかに依存するのです。

では、質問に含まれる引用文を見てください。

<ブロッククオート

相対インポートでは、モジュールの name 属性を使用して、パッケージ階層におけるそのモジュールの位置を決定します。もしモジュールの名前がパッケージ情報を含んでいなければ (例えば 'main' に設定されていれば)、 モジュールがファイルシステム上のどこに実際に位置しているかに関わらず、 モジュールがトップレベルモジュールであるかのように相対インポートが解決されます。

相対インポート...

相対インポートでは、モジュールの 名前 を使用して、パッケージ内の位置を決定します。 のような相対インポートを使用する場合 from .. import foo このドットは、パッケージの階層を何段階か上に移動することを意味します。 たとえば、現在のモジュールの名前が package.subpackage1.moduleX であれば ..moduleA という意味になります。 package.moduleA . の場合は from .. import が動作するためには、モジュール名には少なくとも import ステートメントを使用します。

... は、パッケージの中で相対的なものでしかありません

しかし、モジュール名が __main__ の場合、パッケージに含まれるとはみなされません。 その名前にはドットがありません。 from .. import ステートメントを含むことができます。 これを実行しようとすると、"relative-import in non-package" というエラーが発生します。

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

おそらくあなたがしたことは moduleX などをコマンドラインから実行します。 その際、その名前が __main__ これは、その名前からパッケージに含まれていることが分からないため、その中での相対インポートに失敗することを意味します。これは、モジュールがあるのと同じディレクトリから Python を実行し、そのモジュールをインポートしようとした場合にも起こることであることに注意してください。

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

2つの解決策

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

  2. あるいは、実際には 実行 moduleX のように、他のスクリプトを実行したいだけなら、次のようにします。 myfile.py は、その が使用します。 の中にある関数 moduleX . そのような場合は myfile.py 他の場所 - ない の中にある package ディレクトリに移動して実行します。 もし myfile.py のようなことをすると from package.moduleA import spam であれば、問題なく動作します。

備考

  • これらのソリューションのいずれにおいても、パッケージ・ディレクトリ ( package は、Pythonモジュールの検索パス( sys.path ). そうでない場合は、パッケージの中の何かを確実に使用することは全くできません。

  • Python 2.6 からは、パッケージ解決のためのモジュールの "name" は、その __name__ 属性だけでなく __package__ 属性があります。 そのため、明示的なシンボルである __name__ を使用して、モジュールの "name" を参照します。 Python 2.6 からはモジュールの "name" は事実上 __package__ + '.' + __name__ または、単に __name__ もし __package__None .)