- ImportError: cannot import name ‘X’ from partially initialized module は、2つのモジュールが互いをimportし合う循環インポートで発生するエラーだ。
- 実務で圧倒的に多いのは、2つのファイルが先頭行でお互いをfrom … importしている形。片方はまだ読み込み途中なので、欲しいクラスがその時点では存在していない。
- 直し方は大きく3つ。importを関数の中へ移すか、型ヒント専用ならTYPE_CHECKINGで囲むか、依存の向きそのものを一方通行に直すかだ。
この4コマで描かれた場面は、複数ファイルに分割し始めたPythonプロジェクトで最初に踏む定番の落とし穴です。Pythonはimport文に出会った時点で、対象ファイルを上から順に実行します。読み込みの途中で別ファイルへ寄り道し、その寄り道先から元のファイルへ戻ってくると、戻ってきた時点のモジュールにはまだ半分しか中身が入っていない状態になります。クラス定義まで到達していなければ、当然そのクラス名は取り出せません。
厄介なのは、このエラーが機能追加のタイミングで突然表面化する点です。片方のファイルに型ヒントを1行足しただけ、あるいはパッケージのなかで再エクスポートを1行足しただけで、それまで一方通行だった依存関係が双方向に変わります。コードのロジックは一切壊れていないのに起動すらしなくなるため、原因を見誤ると調査が長引きます。
本番環境で放置した場合の影響も見過ごせません。循環インポートはアプリケーションの起動時に例外を投げるので、Webアプリならワーカープロセスが立ち上がらず、デプロイ直後に全リクエストが失敗します。ローカルではたまたま別の入口から起動していて再現しなかった、というケースも珍しくないでしょう。4コマの最後で語られているとおり、根本的な処方箋は依存の向きを一方通行に固定することにあります。
- Python ImportError: cannot import name from partially initialized moduleの基本情報
- なぜPython ImportError: cannot import name ‘X’ from partially initialized moduleは発生するのか?
- Python ImportError: cannot import name のメッセージはバージョンで変わる?
- DjangoやFlaskでPython ImportError: cannot import name が出るのはどんなときか?
- Python ImportError: cannot import name のメッセージが違う形で出る場合の対処法は?
- Python ImportError: cannot import name ‘X’ はどうデバッグすればいい?
- Python ImportError: cannot import name を未然に防ぐ設計とは?
- 同じ循環インポートでもPython ImportError: cannot import name にならない書き方があるのはなぜ?
- よくある質問
- この記事と一緒に知っておきたいエラー解決
- 【出典】参考URL
Python ImportError: cannot import name from partially initialized moduleの基本情報
Python ImportError: cannot import name ‘X’ from partially initialized module は、読み込みがまだ完了していないモジュールに対して、その中の名前をfrom ... importで取り出そうとしたときに送出される例外です。Pythonのインタプリタが、循環インポートである可能性が高いと判断してヒントを添えてくれている状態と言えます。
| 項目 | 内容 |
|---|---|
| エラーメッセージ | ImportError: cannot import name ‘X’ from partially initialized module ‘Y’ (most likely due to a circular import) (/path/to/Y.py) |
| 発生する言語・環境 | Python 3.8以降のCPython。この文言はbpo-20490の対応でPython 3.8と3.9に導入された |
| エラーの意味 | 部分的にしか初期化されていないモジュールYから名前Xをimportできない、原因はおそらく循環インポートである、という意味 |
| 主な原因 | 2つ以上のモジュールがトップレベルで互いをimportし合い、依存が輪になっている |
| 解決の基本方針 | トレースバックのFile行を上から順に追い、どのファイルとどのファイルが輪を作っているかを先に特定する |

ファイルを分割した直後に出やすいエラーで、現場でもよく相談を受けます。コードの中身より先に、どのファイル同士が呼び合っているかを図に描くと一気にほどけますよ。
なぜPython ImportError: cannot import name ‘X’ from partially initialized moduleは発生するのか?
Python ImportError: cannot import name ‘X’ from partially initialized module の仕組みは、モジュールが読み込まれてから使えるようになるまでの流れを追うと理解できます。Pythonはimport文を見つけると、対象モジュールをsys.modulesへ空の状態で登録し、そのうえでファイルの中身を上から実行します。実行が最後まで終わって初めて、そのモジュールの中の名前が揃います。
ここで別のモジュールへ寄り道し、寄り道先が元のモジュールを再びimportすると、Pythonはsys.modulesにすでに登録済みの中途半端なモジュールを返します。名前がまだ定義されていなければ取り出しに失敗し、循環インポートを示唆するこのエラーになるわけです。以下の3パターンは、その輪ができる典型的な作られ方を読み込み順に沿って並べたものになります。なお再現例の実行結果はいずれもPython 3.12.7のもので、Python 3.13以降の差分は後述します。
パターン1:2つのファイルが先頭行でお互いをimportしている
最も件数が多いのが、機能ごとにファイルを分けた直後に起きるこの形です。ユーザーと注文のように、業務上どうしても関係し合う2つの概念を別ファイルへ切り出したときに生まれます。
# ===== user.py =====
from order import Order # ← この行でorder.pyの実行が始まる
class User:
def __init__(self, name):
self.name = name
self.orders = []
def add_order(self, item):
self.orders.append(Order(self, item))
# ===== order.py =====
from user import User # ← user.pyはまだ1行目の途中でUserが未定義
class Order:
def __init__(self, user, item):
self.user = user
self.item = item
# ===== main.py =====(python main.py で実行 / Python 3.12.7)
from user import User
print(User("taro").name)
# 実行結果
# ImportError: cannot import name 'User' from partially initialized module 'user'
# (most likely due to a circular import) (/home/app/user.py)
読み込み順を追うと、main.pyがuser.pyを呼び、user.pyの1行目がorder.pyを呼び、order.pyの1行目がuser.pyへ戻っています。この時点でuser.pyは1行目を実行している最中であり、classキーワードにすらたどり着いていません。Orderを実際に使うのはadd_orderが呼ばれたときだけなので、importをメソッドの中へ動かせば輪は消えます。
# ===== user.py(修正後)=====
class User: # トップレベルのimportを削除した
def __init__(self, name):
self.name = name
self.orders = []
def add_order(self, item):
from order import Order # ← 呼び出された瞬間に初めて解決する遅延import
self.orders.append(Order(self, item))
# ===== order.py(修正後)=====
class Order: # from user import User の行を削除した
def __init__(self, user, item):
self.user = user
self.item = item
パターン2:パッケージの__init__.pyでの再エクスポートが輪を作る
パッケージを整備して__init__.pyに窓口をまとめると、書いた本人も気づきにくい輪ができあがります。行の並び順ひとつで発生したりしなかったりするため、レビューをすり抜けやすいのが特徴でしょう。
# ディレクトリ構成
# myproject/
# ├── main.py
# └── myapp/
# ├── __init__.py
# ├── models.py
# └── services.py
# ===== myapp/__init__.py =====
from myapp.services import create_user # ← 1行目でservices.pyの実行が始まる
from myapp.models import User # ← Userがmyappに載るのは2行目
# ===== myapp/services.py =====
from myapp import User # ← パッケージ経由でUserを取りに行く
def create_user(name):
return User(name)
# ===== myapp/models.py =====
class User:
def __init__(self, name):
self.name = name
# ===== main.py =====
import myapp
# 実行結果
# ImportError: cannot import name 'User' from partially initialized module 'myapp'
# (most likely due to a circular import) (/home/app/myproject/myapp/__init__.py)
ここでの犯人はservices.pyのfrom myapp import Userです。パッケージ本体を経由して名前を取りに行くと、__init__.pyの何行目まで実行が進んだかに結果が左右されます。サブモジュールを直接指定すれば、パッケージ側の進行度とは無関係に解決できます。
# ===== myapp/services.py(修正後)=====
from myapp.models import User # ← パッケージ本体ではなくサブモジュールを直接指定
def create_user(name):
return User(name)
# __init__.py は行の順序を入れ替えなくてもそのまま動く
# from myapp.services import create_user
# from myapp.models import User
パターン3:型ヒントのためだけに実行時のimportを書いている
型ヒントを丁寧に書くチームほど遭遇するのがこのパターンになります。引数の型を明示したいという動機だけで、実行時にはまったく不要なimportを増やしてしまうケースです。
# ディレクトリ構成
# shop/
# ├── __init__.py (空ファイル)
# ├── models.py
# └── repository.py
# ===== shop/models.py =====
from shop.repository import UserRepository # 実行時に本当に必要なimport
class User:
def __init__(self, name):
self.name = name
def save(self):
UserRepository().save(self)
# ===== shop/repository.py =====
from shop.models import User # ← 型ヒントにしか使っていないimport
class UserRepository:
def save(self, user: User) -> None:
print(user.name)
# ===== main.py =====
from shop.models import User
# 実行結果
# ImportError: cannot import name 'User' from partially initialized module 'shop.models'
# (most likely due to a circular import) (/home/app/shop/models.py)
repository.py側のUserは、注釈として書かれているだけで実行時には一度も評価されません。typing.TYPE_CHECKINGは実行時に必ずFalseになる定数で、静的型チェッカーだけがTrueとみなして中身を読みます。型チェッカーには見せて、インタプリタには見せないimportを書き分けられるわけです。
# ===== shop/repository.py(修正後)=====
from __future__ import annotations # 注釈を実行時に評価しない(Python 3.7以降で利用可能)
from typing import TYPE_CHECKING
if TYPE_CHECKING: # 実行時は必ずFalseなので、この行は実行されない
from shop.models import User # 型チェッカーだけがこのimportを読む
class UserRepository:
def save(self, user: User) -> None:
print(user.name)

3つ目の型ヒント由来のパターン、意外と多いんです。実行時に本当に必要なimportかどうかを一度問い直すだけで、かなりの数が消えていきます。
Python ImportError: cannot import name のメッセージはバージョンで変わる?
Python ImportError: cannot import name ‘X’ from partially initialized module の文言は、CPythonのバージョンとファイルの置き場所によって切り替わります。検索したメッセージと手元の表示が一致しないときは、まず実行中のバージョンを確認してください。次の表は、CPythonのPython/ceval.cにある書式文字列を確認したうえで整理したものです。
| 実行環境 | 表示されるエラーメッセージ |
|---|---|
| Python 3.8〜3.12 | cannot import name ‘X’ from partially initialized module ‘Y’ (most likely due to a circular import) (/path/to/Y.py) |
| Python 3.8〜3.12でファイル位置が不明な場合 | cannot import name ‘X’ from partially initialized module ‘Y’ (most likely due to a circular import) |
| Python 3.13以降で、対象ファイルがsys.path[0]直下にある場合 | cannot import name ‘X’ from ‘Y’ (consider renaming ‘/path/to/Y.py’ if it has the same name as a library you intended to import) |
| Python 3.13以降で、対象ファイルがパッケージ配下やsite-packagesにある場合 | cannot import name ‘X’ from partially initialized module ‘Y’ (most likely due to a circular import) (/path/to/Y.py) |
| Python 3.13以降で、モジュール名が標準ライブラリと同名の場合 | cannot import name ‘X’ from ‘Y’ (consider renaming ‘/path/to/Y.py’ since it has the same name as the standard library module named ‘Y’ and prevents importing that standard library module) |
3.13以降の判定に使われるのは、対象モジュールのファイルが置かれたディレクトリがsys.path[0]と一致するかどうかです。python main.pyのようにスクリプトを直接実行した場合、スクリプトのあるディレクトリがsys.path[0]になるため、同じ階層に置いた自作モジュールは名前衝突を疑うメッセージ側へ回されます。循環インポートなのにファイル名の変更を勧められるのは、この判定が働いているからです。挙動はCPythonのIssue 136094として報告されています。
DjangoやFlaskでPython ImportError: cannot import name が出るのはどんなときか?
Djangoでの発生パターン
Django ImportError: cannot import name ‘X’ from partially initialized module は、複数アプリのモデルが相互に外部キーを張る場面で表面化します。blog/models.pyがshop.models.Productをimportし、shop/models.py側もblog.models.Articleを参照していると、django.setup()がモデルを読み込む段階で輪が閉じてしまいます。Djangoにはこの問題を回避するための遅延参照が用意されており、モデルクラスの代わりにアプリラベル付きの文字列を渡せるようになっています。
# ===== blog/models.py(NG例)=====
from django.db import models
from shop.models import Product # ← shop側もblogをimportしていると循環する
class Article(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
# ===== blog/models.py(修正後)=====
from django.db import models
class Article(models.Model):
# モデルクラスをimportせず、アプリラベル.モデル名の文字列で遅延参照する
product = models.ForeignKey("shop.Product", on_delete=models.CASCADE)
Flaskでの発生パターン
Flask ImportError: cannot import name ‘app’ from partially initialized module は、アプリケーションをパッケージ化したときに現れます。views.pyは__init__.pyで作られたappオブジェクトを必要とし、__init__.pyはルートを登録するためにviews.pyを読み込む必要があるという、避けようのない相互依存が生じるからです。Flaskの公式ドキュメントもこれを循環インポートだと明言したうえで、appオブジェクトを作り終えた後、ファイル末尾でviewsをimportすれば問題ないと説明しています。
# ===== yourapplication/__init__.py(NG例)=====
from flask import Flask
import yourapplication.views # ← appを作る前にviewsを読むのでImportErrorになる
app = Flask(__name__)
# ===== yourapplication/__init__.py(修正後)=====
from flask import Flask
app = Flask(__name__)
import yourapplication.views # ← appを作り終えた後、ファイル末尾でimportする
# ===== yourapplication/views.py =====
from yourapplication import app
@app.route("/")
def index():
return "Hello World!"
Python ImportError: cannot import name のメッセージが違う形で出る場合の対処法は?
partially initialized module ‘X’ has no attribute ‘Y’ と表示される場合
Python ImportError: cannot import name とほぼ同じ原因でありながら、例外の種類がAttributeErrorに変わることがあります。from a import runではなくimport aと書いた場合、モジュールオブジェクトの取得自体は成功し、その後の属性アクセスで失敗するからです。エラー名は違っても、読むべき場所と直し方は共通しています。
# ===== a.py =====
import b # from ... import ではなくモジュールごとimport
def run():
return b.helper()
# ===== b.py =====
import a
def helper():
return "ok"
value = a.run() # ← 読み込み途中のa.pyの関数をトップレベルで呼んでいる
# 実行結果(python -c "import a" / Python 3.12.7)
# AttributeError: partially initialized module 'a' from '/home/app/a.py'
# has no attribute 'run' (most likely due to a circular import)
# ===== b.py(修正後)=====
import a
def helper():
return "ok"
def get_value(): # ← import時ではなく、呼び出し時にa.runを触る
return a.run()
consider renaming … と表示される場合
Python 3.13以降で循環インポートを起こすと、cannot import name ‘X’ from ‘Y’ (consider renaming … if it has the same name as a library you intended to import) という文言に出会うことがあります。ファイル名を変えろという助言に見えますが、スクリプトと同じ階層に置いた自作モジュール同士が循環しているだけ、というケースが大半でしょう。まずは本当に名前衝突なのかを切り分けてください。
# 切り分け1: 標準ライブラリと同名のファイルがないか確認する
$ python -c "import sys; print(sorted(sys.stdlib_module_names)[:20])"
$ ls *.py
# 切り分け2: スクリプトのディレクトリをsys.pathへ入れずに実行する(-PはPython 3.11以降)
$ python -P main.py
# ここで partially initialized module ... circular import の文言に変われば、
# 名前衝突ではなく循環インポートが原因だと確定できる
Python ImportError: cannot import name ‘X’ はどうデバッグすればいい?
Python ImportError: cannot import name ‘X’ from partially initialized module のデバッグでは、トレースバックを下から読むという普段の習慣をいったん捨てましょう。このエラーのトレースバックに並ぶFile行は、import文をたどった順路そのものであり、循環の輪をそのまま図示してくれているからです。上から順に読み、同じファイル名が2回登場する箇所を探せば犯人が決まります。
# トレースバックの読み方(同じファイル名が2回出てくる箇所が輪の入口と出口)
Traceback (most recent call last):
File "/home/app/main.py", line 1, in <module>
from user import User
File "/home/app/user.py", line 1, in <module> # ← 1回目のuser.py
from order import Order
File "/home/app/order.py", line 1, in <module>
from user import User # ← ここでuser.pyへ戻る
ImportError: cannot import name 'User' from partially initialized module 'user' (most likely due to a circular import) (/home/app/user.py)
# importの連鎖を時系列で可視化する(-X importtime はPython 3.7以降)
$ python -X importtime main.py
# 読み込み済みモジュールを一覧して、どこまで進んだかを確認する
import sys
print("now loading:", __name__)
print("loaded:", sorted(m for m in sys.modules if m.startswith("myapp")))

トレースバックを上から読む、これだけで解決時間が段違いに縮みます。同じファイル名が2回出てきたら、そこが輪の閉じ目だと思ってください。
Python ImportError: cannot import name を未然に防ぐ設計とは?
Python ImportError: cannot import name ‘X’ from partially initialized module を根本から断つ方法は、依存の向きを一方通行に固定することに尽きます。データを表すモジュールは他の自作モジュールをimportしない最下層に置き、業務処理はその上、外部へ公開する入口はさらにその上、という順序を決めておけば、輪は原理的に生まれません。あわせて静的解析を回し、逆流が起きた瞬間に検知できるようにしておきましょう。
# 依存が双方向になっている状態(避けたい形)
# models.py ←→ services.py
# 依存を一方通行に固定した状態(目指す形)
# api.py → services.py → models.py
# ===== myapp/models.py =====(最下層。他の自作モジュールをimportしない)
class User:
def __init__(self, name):
self.name = name
# ===== myapp/services.py =====(下の層だけをimportする)
from myapp.models import User
def create_user(name):
return User(name)
# ===== myapp/api.py =====(さらに上の層。下の層は自由にimportしてよい)
from myapp.services import create_user
# 循環importを自動検出する(pylintのR0401)
$ pylint --disable=all --enable=cyclic-import myapp
# 出力例
# ************* Module myapp
# myapp/services.py:1:0: R0401: Cyclic import (myapp.models -> myapp.services) (cyclic-import)
同じ循環インポートでもPython ImportError: cannot import name にならない書き方があるのはなぜ?
Python ImportError: cannot import name ‘X’ from partially initialized module が出るかどうかは、循環しているかどうかだけでは決まりません。分かれ目はimport文の書き方にあります。import orderと書いた場合、Pythonが行うのはモジュールオブジェクトを名前に束ねる作業だけで、中身の完成度は問われません。読み込み途中の空っぽのモジュールでも、束ねること自体は成功します。
一方from order import Orderは、モジュールを取得したうえで、そこからOrderという属性を取り出すところまでを一度に行います。属性の取り出しは中身が揃っていなければ失敗するので、循環していると即座にImportErrorになります。同じ循環でも前者が通り、後者が落ちるのはこのためです。
この違いを利用して、import文をファイルの末尾へ移す、あるいはfrom形式をやめてモジュール参照に切り替える、という応急処置がよく使われます。ただしこれらは輪そのものを残したままの延命策にすぎません。誰かが実行順を1行入れ替えた瞬間に再発しますし、パターン2で見たように__init__.pyの行順へ依存する設計は非常に壊れやすくなります。応急処置で本番を止めずに済ませたら、その日のうちに依存の向きを整理し直すところまでを一続きの作業と考えることが求められます。

応急処置で動いたときこそ危ないタイミングです。落ち着いて依存関係の図を1枚描いておけば、次の担当者も救われますよ。
よくある質問
-
Qローカルでは動くのに、本番のコンテナ起動時だけImportErrorが出るのはなぜですか?
-
A
入口となるモジュールが違うため、同じコードでもimportされる順序が変わるからです。ローカルでは
python main.pyから起動し、本番ではWSGIサーバーが別のモジュールを最初に読み込む、という構成では循環の輪に入る向きが逆になります。輪が残っている限りどこかの入口で必ず露見するので、入口を揃えるのではなく輪そのものを解消してください。
-
QDjangoでsignals.pyをmodels.pyからimportすると循環しますが、正しい置き場所はどこですか?
-
A
アプリの
apps.pyに用意されているAppConfig.ready()の中でimportするのが定石です。ready()はアプリのレジストリが完成した後に呼ばれるため、モデルの読み込み途中へ割り込む心配がありません。models.pyの末尾でimportする方法もありますが、行の順序に依存する分だけ壊れやすくなります。
-
Q実行する前にLinterで循環インポートを見つけることはできますか?
-
A
pylintのcyclic-import(メッセージID R0401)で検出できます。2つ以上のモジュールが直接または間接に循環していることを検知するチェックで、プログラムを実行しなくても静的に警告してくれます。CIへ組み込む場合は
--disable=all --enable=cyclic-importのように絞って実行すると、既存プロジェクトへも段階的に導入しやすくなるでしょう。
-
QPython 3.14では型ヒント用のfrom __future__ import annotationsは不要になりますか?
-
A
前方参照を文字列で囲む必要はなくなりますが、TYPE_CHECKINGブロック自体は引き続き必要です。Python 3.14ではPEP 649により、関数やクラスの注釈が即座に評価されず、必要になったときに初めて評価される仕組みへ変わりました。とはいえ実行時に本当にimportしてしまえば循環は復活するので、実行時不要なimportを条件分岐で隔離する考え方は変わりません。
-
QPython ImportError: cannot import name とModuleNotFoundErrorの違いは何ですか?
-
A
モジュールを見つけられたかどうかが決定的な違いです。ModuleNotFoundErrorはImportErrorのサブクラスで、モジュールそのものが発見できなかったときに送出されます。対してcannot import name 系のImportErrorは、モジュールは見つかっているのにその中の名前を取り出せなかった状態を指します。前者はパスや環境の問題、後者はコードの構造や依存関係の問題、と切り分けると原因調査が速くなります。
この記事と一緒に知っておきたいエラー解決
| 関連エラー | この記事との関連 |
|---|---|
| Python ModuleNotFoundError: No module named ‘…’ | 同じimport系のエラーで、モジュール自体が見つからない場合はこちらになります。 |
| Python AttributeError: has no attribute | 循環インポートがimport module形式で起きると、AttributeErrorとして現れます。 |
| Python NameError: name ‘X’ is not defined | importに失敗した名前を後続の行で使うと、続けてこのエラーが発生します。 |
| Ruby LoadError: cannot load such file | 他言語で読み込みに失敗したときの代表例で、切り分けの考え方が共通します。 |
| TypeScript Cannot find module | モジュール解決の失敗という点で似ており、依存の向きを整理する発想も同じです。 |
【出典】参考URL
https://docs.python.org/3/library/exceptions.html :ImportErrorの定義とname・path属性の記述
https://github.com/python/cpython/blob/3.12/Python/ceval.c :Python 3.12のimport_fromが生成するエラーメッセージ書式
https://github.com/python/cpython/blob/3.13/Python/ceval.c :Python 3.13で分岐が追加されたエラーメッセージ書式
https://github.com/python/cpython/blob/3.13/Objects/moduleobject.c :partially initialized moduleのAttributeError文言と_PyModule_IsPossiblyShadowingの判定条件
https://bugs.python.org/issue20490 :循環インポート向けメッセージがPython 3.8と3.9へ導入された経緯
https://github.com/python/cpython/pull/15308/files :導入前後のメッセージ書式の差分
https://github.com/python/cpython/issues/136094 :Python 3.13で循環インポート時にファイル名変更を勧められる挙動の報告
https://docs.python.org/3/using/cmdline.html :-X importtimeと-P(PYTHONSAFEPATH)の仕様と追加バージョン
https://docs.python.org/3/whatsnew/3.14.html :PEP 649による注釈の遅延評価と、文字列化が不要になった旨
https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING :TYPE_CHECKINGが実行時にFalseになる仕様
https://docs.djangoproject.com/en/5.2/ref/models/fields/#django.db.models.ForeignKey :ForeignKeyの遅延参照(文字列指定)
https://flask.palletsprojects.com/en/stable/patterns/packages/ :Flaskにおける循環インポートと末尾importの解説
https://pylint.readthedocs.io/en/stable/user_guide/messages/refactor/cyclic-import.html :cyclic-import(R0401)の内容

コメント