HOWTO · Python

Python で既知の親パッケージがない相対インポートの試みを解決する

このチュートリアルでは、Python で ImportError を解決し、相対インポートを処理する方法について説明します。

このページの内容

ImportError: attempted relative import with no known parent package は、先頭にドットが付いたインポートに必要なパッケージコンテキストがない状態で、Python がモジュールを実行していることを示します。通常は、そのモジュールをパッケージ経由で実行するか、プロジェクトの構成に合った絶対インポートを使うと解決できます。__init__.py を追加するだけでは、ファイルを直接実行したときに親パッケージが設定されるわけではありません。

Python でこの ImportError が発生する理由

from .helpers import format_name のような相対インポートは、現在のモジュールが属するパッケージを起点にします。モジュールがパッケージの一部としてインポートされると、Python はそのパッケージを認識できます。一方、ファイルを直接起動すると、その名前は __main__ になり、通常はパッケージ名がありません。そのため、パッケージのディレクトリやインポート先のファイルが存在していても、python path/to/module.py ではエラーになることがあります。

インポート構文自体も、有効な from インポートでなければなりません。import .module_name は Python の構文として無効です。from . import module_name や from .module_name import function_name のように書いてください。先頭のドットは「このパッケージを基準にする」という意味であり、「ファイルの起動方法に関係なく同じディレクトリを探す」という意味ではありません。

モジュールをパッケージのコンテキストで実行する

myPackage という名前のパッケージなら、ファイルを1つのプロジェクトディレクトリの下に配置します。たとえば、IError/myPackage/__init__.py、IError/myPackage/myNewPackage.py、IError/myPackage/main.py のようにします。main.py では、相対インポートを from .myNewPackage import createSingleDict と書けます。myPackage を含むディレクトリ IError から、python -m myPackage.main を実行します。-m オプションを使うと、Python はモジュールをパッケージの一部として検索して実行するため、相対インポートの親パッケージが認識されます。

相対インポートを使うファイルを、myPackage/main.py のようにファイルパスで起動しないでください。また、__init__.py を直接実行するのも避けてください。このファイルは、Python がパッケージをインポートするときに初期化を行うもので、パッケージのエントリーポイントではありません。

プロジェクトに合う場合は絶対インポートを使う

絶対インポートでは、インポート検索パス上のモジュールを指定します。たとえば、from myPackage.myNewPackage import createSingleDict のように書きます。プロジェクトですでにパッケージルートからのインポートを使っており、そのパッケージルートが sys.path 上にある場合は、絶対インポートが適しています。たとえば、プロジェクトのルートから実行するか、環境にパッケージをインストールしている場合です。

アプリケーションと起動方法に合ったインポート形式を選んでください。絶対インポートに変えても、Python が見つけられないパッケージ名の問題は解決しません。指定したパッケージを現在の環境からインポートできるか確認してください。

パッケージ構成と作業ディレクトリを確認する

インポートで指定したディレクトリが読み込みたいパッケージであること、モジュールのファイル名がインポート文と一致すること、想定したプロジェクトルートから Python を起動していることを確認してください。上の例では、作業ディレクトリは IError/myPackage ではなく IError で、実行するコマンドは python -m myPackage.main です。パッケージがさらに階層化されている場合は、project を含むディレクトリから python -m project.tools.runner のように、モジュールパスに各パッケージ名を含めます。

通常のパッケージには __init__.py が含まれることが多いですが、適切な構成であれば Python は名前空間パッケージもサポートします。原因を調べるうえで重要なのは、モジュールの実行方法です。パッケージディレクトリ内にあるファイルでも、直接起動するとパッケージコンテキストを持たないことがあります。ターミナル、IDE、テストランナーでインポートの挙動が異なる場合は、使用中のインタープリターと環境も確認してください。

最小例で修正を確認する

次の自己完結型の例では、一時ディレクトリに小さなパッケージを作成し、エントリーモジュールを python -m で起動します。Python でこの例を実行すると、インポートした関数が返す値が表示されます。

from pathlib import Path
import subprocess
import sys
import tempfile

with tempfile.TemporaryDirectory() as temporary_directory:
    project = Path(temporary_directory)
    package = project / "myPackage"
    package.mkdir()
    (package / "__init__.py").write_text("", encoding="utf-8")
    (package / "myNewPackage.py").write_text(
        'def createSingleDict(name, value):\n'
        '    return {"name": name, "value": value}\n',
        encoding="utf-8",
    )
    (package / "main.py").write_text(
        "from .myNewPackage import createSingleDict\n"
        'print(createSingleDict("Jacob", 25))\n',
        encoding="utf-8",
    )
    result = subprocess.run(
        [sys.executable, "-m", "myPackage.main"],
        cwd=project,
        check=True,
        capture_output=True,
        text=True,
    )
    print(result.stdout, end="")

コマンドは一時プロジェクトのルートを作業ディレクトリとして実行するため、Python は myPackage を見つけ、main をパッケージモジュールとして読み込めます。この方法を自分のプロジェクトに応用する場合は、-m の後に指定するモジュールパスを、起動元ディレクトリ以下のフォルダー構成に合わせてください。

必要に応じて init.py からサブモジュール名を公開する

パッケージレベルでのエクスポートは、使いやすさのための任意の機能です。呼び出し側から myPackage.createSingleDict のように名前を使えるようにするには、__init__.py に from .myNewPackage import createSingleDict を記述します。すると、myPackage をインポートしたときにその関数も利用できます。複数のモジュールがある場合は、たとえば from .otherPackage import printName のように、それぞれから必要な名前を __init__.py にインポートできます。

このようなエクスポートによって、パッケージの名前空間から使いやすい名前を選べます。ただし、ファイルを直接起動したときにパッケージコンテキストが設定されるわけではありません。その問題には、パッケージとして実行するか、適切な絶対インポートを使って対処してください。