10億回目の相対インポート 質問する

10億回目の相対インポート 質問する

私はここに来ました:

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

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

ImportError: 既知の親パッケージがない相対インポートを試行しました

私は 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 つに、次の応答がありました。

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

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

Python はなぜそのエラー メッセージを表示するのでしょうか? 「非パッケージ」とはどういう意味ですか? 「パッケージ」を定義する理由と方法を教えてください。

ベストアンサー1

スクリプトとモジュール

説明は次のとおりです。簡単に言うと、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__

モジュールをパッケージ経由でアクセスしない

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

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

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

さて、質問に含めた引用文を見てみましょう。

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

相対インポート...

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

...パッケージ内では相対的なものに過ぎない

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

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

おそらく、moduleXコマンドラインから などを実行しようとしたのでしょう。これを実行すると、その名前は に設定され__main__、その名前からはパッケージ内にあることが分からないため、その内部での相対インポートは失敗します。モジュールがあるのと同じディレクトリから Python を実行し、そのモジュールをインポートしようとした場合にも、同じことが起こることに注意してください。これは、前述のように、Python はモジュールがパッケージの一部であることを認識せずに、現在のディレクトリでモジュールを「早すぎる」タイミングで見つけてしまうためです。

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

2つの解決策:

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

  2. あるいは、実際には を実行し たいのではなく、内の関数を使用するmoduleXなどの他のスクリプトを実行したいだけかもしれません。その場合は、 をディレクトリ内でなく別の場所に置いて実行してください。 内で のようなことを行う、正常に動作します。myfile.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

おすすめ記事