web-dev-qa-db-ja.com

10億回目の相対輸入

私はここにいました:

すぐに解決策が見つかると思ったときに、コピーしなかったたくさんのURL、一部のURL、他のサイトのURLなど。

永遠に繰り返される質問はこれです:Windows 7、32ビットPython 2.7.3では、どのように私はこの「パッケージ外の相対インポートの試み」というメッセージを解決するのですか?私は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がそのエラーメッセージを出すのかそれが非パッケージであることを意味する!、なぜそしてどうやって 'package'を定義するのか、そして正確な答えは、幼稚園児が理解するのに十分なほど簡単に言い表すことができる

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

443
user1881400

スクリプトとモジュール

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

Pythonファイルをロードする方法は2つあります。トップレベルのスクリプトとして、またはモジュールとしてです。ファイルを直接実行する場合、たとえばコマンドラインでpython myfile.pyと入力して、ファイルを最上位スクリプトとしてロードします。 python -m myfileを実行した場合、または他のファイル内でimportステートメントが見つかったときにロードされた場合は、モジュールとしてロードされます。一度に1つのトップレベルのスクリプトしか存在できません。トップレベルのスクリプトは、作業を開始するために実行したPythonファイルです。

命名

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

だからあなたの例では例えば:

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

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

そのパッケージを含まないモジュールへのアクセス

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

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

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

質問に含まれている見積もりを見てみましょう。

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

相対インポート...

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

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

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

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

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

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

二つの解決策:

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

  2. あるいは、実際にrunmoduleXを実行したくない場合は、他のスクリプトを実行したいだけで、myfile.pyというようにmoduleX内で関数を使用します。その場合、myfile.pypackageディレクトリ内の別の場所に - notに配置して実行します。 myfile.pyの内側でfrom package.moduleA import spamのようなことをしていれば、うまくいきます。

注意事項

  • どちらの方法でも、パッケージディレクトリ(この例ではpackage)はPythonモジュールの検索パス(sys.path)からアクセスできる必要があります。そうでなければ、あなたはパッケージの中で何かを確実に使うことは全くできないでしょう。

  • Python 2.6以降、パッケージ解決のためのモジュールの "名前"は、その__name__属性だけでなく、__package__属性によっても決まります。モジュールの "名前"を参照するために明示的なシンボル__name__を使うのを避けているのはそのためです。 Python 2.6以降、モジュールの "名前"は事実上__package__ + '.' + __name__、または__name__Noneの場合は単に__package__になります。)

709
BrenBarn

これは本当にPythonの問題です。 混乱の原点は、人々が誤って相対インポートを相対パスではないと見なしているということです

例えば、 faa.py と書くと:

from .. import foo

実行中にパッケージの一部として faa.py 識別およびロードされた場合 にのみ意味があります。その場合、 faa.py モジュールの名前は、たとえば some_packagename.faa になります。現在のディレクトリにあるという理由だけでファイルがロードされた場合、pythonが実行されると、その名前はどのパッケージも参照せず、最終的に相対インポートは失敗します。

カレントディレクトリのモジュールを参照する簡単な解決策はこれを使うことです:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo
22
Rami Ka.

これは、例として適合するように修正された一般的なレシピで、パッケージとして記述されたPythonライブラリを処理するために現在使用しています。相互依存ファイルを含んでいます。 。これをlib.fooと呼び、関数lib.fileAおよびf1の場合はf2、クラスlib.fileBの場合はClass3にアクセスする必要があるとしましょう。

これがどのように機能するかを説明するために、いくつかのprint呼び出しを含めました。実際には、それらを削除することもできます(そしておそらくfrom __future__ import print_function行も)。

この特定の例は、エントリをsys.pathに本当に挿入する必要がある場合に表示するには単純すぎます。 (複数のレベルのパッケージディレクトリがあり、os.path.dirname(os.path.dirname(__file__))を使用する場合、doが必要な場合は Larsの回答 を参照してください。しかし、実際にはhurtでもありません。)if _i in sys.pathテストなしでこれを行うのも安全です。ただし、インポートされた各ファイルが同じパスを挿入する場合(たとえば、fileAfileBの両方がパッケージからユーティリティをインポートする場合)、これはsys.pathを同じパスで何度も混乱させます。そのため、ボイラープレートにif _i not in sys.pathを含めると便利です。

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __== '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

ここでの考え方はこれです(これらはすべてpython2.7とpython 3.xで同じように機能することに注意してください):

  1. import libまたはfrom lib import fooを通常のコードからの通常のパッケージインポートとして実行する場合、__packagelibで、__name__lib.fooです。最初のコードパスを使用して、.fileAなどからインポートします。

  2. python lib/foo.pyとして実行すると、__package__はNoneになり、__name____main__になります。

    2番目のコードパスを使用します。 libディレクトリは既にsys.pathにあるため、追加する必要はありません。 fileAなどからインポートします。

  3. libディレクトリ内でpython foo.pyとして実行する場合、動作はケース2と同じです。

  4. libディレクトリ内でpython -m fooとして実行する場合、動作はケース2および3に似ています。ただし、libディレクトリへのパスはsys.pathにないため、インポートする前に追加してください。 Pythonを実行してからimport fooを実行した場合も同様です。

    .issys.pathであるため、ここにパスの絶対バージョンを実際に追加する必要はありません。これは、より深いパッケージのネストです。 from ..otherlib.fileC import ...を実行したい構造が違いを生みます。これを行っていない場合は、sys.path操作をすべて省略できます。

ノート

まだ癖があります。この全体を外部から実行する場合:

$ python2 lib.foo

または:

$ python3 lib.foo

動作はlib/__init__.pyの内容に依存します。それが存在し、が空の場合、すべて正常です:

Package named 'lib'; __is '__main__'

しかし、lib/__init__.pyitselfroutine.namelib.nameとして直接エクスポートできるようにroutineをインポートすると、次のようになります。

$ python2 lib.foo
Package named 'lib'; __is 'lib.foo'
Package named 'lib'; __is '__main__'

つまり、モジュールは2回インポートされます。1回はパッケージ経由で、次に__main__として再度インポートされ、mainコードが実行されます。 Python 3.6以降では、これについて警告します。

$ python3 lib.routine
Package named 'lib'; __is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __is '__main__'

warningは新しいですが、警告された動作は新しいものではありません。これは、一部の呼び出し 二重インポートトラップ の一部です。 (詳細については、 issue 27487 を参照してください。)Nick Coghlanは次のように述べています。

この次のトラップは3.3を含むPythonの現在のすべてのバージョンに存在し、次の一般的なガイドラインに要約できます。「パッケージディレクトリまたはパッケージ内のディレクトリをPythonパスに直接追加しないでください。 「。

ここでそのルールに違反している間、ロードされるファイルがnotの一部としてロードされる場合にのみonlyこのパッケージ内の変更は、そのパッケージ内の他のファイルにアクセスできるように特別に設計されています。 (そして、私が述べたように、おそらく単一レベルのパッケージに対してこれを行うべきではありません。)さらにクリーンにしたい場合は、これを次のように書き換えることができます。

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

つまり、インポートを達成するのに十分な長さsys.pathを変更し、元の状態に戻します(_iのコピーを1つ追加した場合に限り、_iのコピーを1つ削除します) 。

6
torek

他の多くの人と一緒にこのことについて考えた後、私はこの 記事Dorian B によって投稿された私が抱えていた特定の問題を解決したメモに出会いましたここではWebサービスで使用するためのモジュールとクラスを開発しますが、PyCharmのデバッガ機能を使用して、コーディング中にそれらをテストできるようにしたいと思います。自己完結型クラスでテストを実行するには、クラスファイルの最後に次のコードを追加します。

if __== '__main__':
   # run test code here...

しかし、同じフォルダに他のクラスやモジュールをインポートしたい場合は、すべてのインポート文を相対表記法からローカル参照に変更する必要があります(つまり、ドット(。)を削除します)。ワンライナー 'そしてそれはうまくいった!このクラスを別のテスト対象クラスで使用するとき、または自分のWebサービスで使用するときは、PyCharmでテストしてテストコードをそのままにすることができます。

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __== '__main__' or parent_module.__== '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

Ifステートメントは、このモジュールを main として実行しているかどうか、または main としてテストされている別のモジュールで使用されているかどうかを確認します。 。おそらくこれは明白ですが、上記の相対的なインポートの問題に不満を抱いている他の誰かがそれを利用できる場合に備えて、私はここでこのメモを提供します。

3
Steve L

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

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

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

#!/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 __== '__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 *
2
Lars

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

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

コードがグローバル名前空間から実行されているかどうかをテストするためにif __== '__main__'を使った多くのpythonコードが見られます - スクリプトを兼ねるモジュールを持つことができます。

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

2
theodox

これは私がお勧めしない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()
2
Federico

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

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

1
ec2604

Pythonが私に戻ってこないようにするには「非パッケージで相対インポートを試みました」。パッケージ/

init.py subpackage1 /init.py moduleX.py moduleY.py subpackage2 /init.py moduleZ.py moduleA.py

このエラーは、親ファイルに相対インポートを適用している場合にのみ発生します。例えば、 "print(nameをコーディングした後、親ファイルは既にmainを返します。このファイルは既にmainであり、それ以降の親パッケージを返すことはできません。パッケージsubpackage1およびsubpackage2のファイルには相対インポートが必要です。親ディレクトリまたはモジュールを参照するために ".."を使用できます。ただし、親パッケージがすでに最上位のパッケージである場合は、その親ディレクトリ(パッケージ)より上位に移動できません。あなたが親に相対的なインポートを適用しているそのようなファイルは絶対インポートのアプリケーションで働くことができるだけです。プロジェクトのトップレベルを定義するPYTHON PATHの概念のために、あなたのファイルがサブパッケージに入っていても、あなたが親パッケージの絶対インポートを使用する場合、エラーは発生しません。

0
Sakshi Jain

@BrenBarnの答えはそれをすべて言っていますが、あなたが私のようなら、理解するのに時間がかかるかもしれません。これが私の場合と、@ BrenBarnの答えがどのように適用されるかを示しています。おそらく役立つでしょう。

ケース

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

使い慣れた例を使用して、それにmoduleX.pyが..moduleAへの相対的なインポートを持つことを追加します。 moduleXをインポートしたsubpackage1ディレクトリにテストスクリプトを記述しようとしたが、OPに記述された恐ろしいエラーが発生したことを考えてみてください。

ソリューション

テストスクリプトをパッケージと同じレベルに移動し、package.subpackage1.moduleXをインポートします

説明

説明したように、相対的なインポートは現在の名前に対して相対的に行われます。テストスクリプトが同じディレクトリからmoduleXをインポートする場合、moduleX内のモジュール名はmoduleXです。相対的なインポートを検出すると、インタープリターはパッケージの階層をバックアップできません。これは既に最上位にあるためです。

上記からmoduleXをインポートすると、moduleX内の名前はpackage.subpackage1.moduleXになり、相対インポートが見つかります

0
Brad Dre