長くなってきたファイルを、思いきって2つに分けた。そのとたんにプログラムが動かなくなった経験はありませんか。
画面に出るのは、見慣れないこんな一文です。
ImportError: cannot import name 'User' from partially initialized module 'user'
(most likely due to a circular import)
整理したはずなのに壊れたのですから、気持ちが折れます。これが循環インポートと呼ばれる問題です。
今回はこのエラーのしくみと、4つの直し方を順番に見ていきます。読み終わるころには、出ても落ち着いて対処できるようになっているはずです。
循環インポートは、2つのファイルが互いを呼び合う状態¶
名前のとおり、インポートが輪になってしまった状態のことです。
AがBを読み込み、そのBがAを読み込む。ぐるっと一周して戻ってきてしまいます。
具体的に見たほうが早いので、小さな例を作りました。利用者を扱うuser.pyと、注文を扱うorder.pyの2枚です。
# user.py
from order import format_order
class User:
def __init__(self, name):
self.name = name
def summary(self, data):
return f'{self.name}様: {format_order(data)}'
# order.py
from user import User
def format_order(data):
return f'{data["item"]} x{data["qty"]}'
def make_guest_order(item):
return {'user': User('ゲスト'), 'item': item, 'qty': 1}
どちらのファイルも、自然な書き方に見えませんか。実際、設計としてはよくある形です。
それでもfrom user import Userを実行した瞬間に、先ほどのエラーが出ます。
なぜ未完成のモジュールを読んでしまうのか¶
ここがいちばん大事なところなので、ゆっくり説明します。
Pythonにとってモジュールは、上から順に実行される1本のスクリプトです。読み込むとは、そのファイルを頭から走らせることを意味します。
さきほどの例で何が起きたかを追ってみましょう。
| 順番 | 起きていること |
|---|---|
| 1 | user.pyの実行が始まる。まだUserは作られていない |
| 2 | 1行目のfrom order import format_orderでorder.pyへ移る |
| 3 | order.pyの1行目がfrom user import Userを求める |
| 4 | user.pyは実行中なので、Pythonは途中まで作った中身を渡そうとする |
| 5 | そこにUserはまだ無い。よってエラーになる |
エラー文にある partially initialized module は、この4番の状態を指しています。壊れているのはファイルではなく、読み込む順番のほうです。
ここを理解すると、次の一文の意味もはっきりします。輪になっていること自体ではなく、一周して戻ったときに必要なものがまだ作られていないことが問題なのです。
ModuleNotFoundErrorとは別のエラー¶
似た場面で出るエラーと混同しやすいので、区別しておきます。
ModuleNotFoundErrorは、そもそもファイルが見つからないという意味です。今回のImportErrorは、ファイルは見つかったけれど中身がまだ無い、という意味になります。
前者で困っている方は、こちらの記事が役に立つはずです。【関連記事】PythonのModuleNotFoundErrorって何?解決するにはどうしたらいいの?
tracebackは下から2行目を見る¶
原因の特定は、エラー文の読み方さえわかれば一瞬です。
tracebackには、読み込みをたどった道のりがそのまま並びます。どのファイルからどのファイルへ飛んだのかが、上から順に書かれています。
その並びの中に同じファイル名が2回出てきたら、それが輪の証拠です。エラー文の読み方そのものは、こちらで詳しく扱いました。【関連記事】Pythonのエラー文はどこを読めばいい?初心者向けtracebackの見方
直し方その1:共通の部分を3枚目に切り出す¶
いちばんおすすめの方法から紹介します。輪をほどくのではなく、そもそも輪を作らない形に変える方法です。
さきほどの例で、2つのファイルが本当に共有したかったのはformat_orderという表示の処理だけでした。ならば、それを別のファイルへ移します。
# formatting.py
def format_order(data):
return f'{data["item"]} x{data["qty"]}'
こうするとuser.pyもorder.pyもformatting.pyを読むだけになり、互いを呼ぶ必要がなくなります。
# user.py
from formatting import format_order
class User:
def __init__(self, name):
self.name = name
def summary(self, data):
return f'{self.name}様: {format_order(data)}'
手元のPython 3.11で実行すると、今度は山田様: ノート x2と正しく表示されました。
私は10年ほどエンジニアとして開発に関わってきましたが、循環インポートが出たときはまずこの方法を検討します。エラーは、責務の置き場所が間違っているという設計からの合図であることが多いからです。
ファイルが太りすぎて分け方に迷っている方は、こちらもあわせてどうぞ。【関連記事】神クラス(God Class)とは?肥大化したコードを分解する方法を解説
直し方その2:両方のimport文を書き換える¶
設計を変えたくない場合は、import文の書き方を変える手もあります。
from order import format_orderではなく、import orderと書く方法です。前者は読み込んだ瞬間に中身を取り出しますが、後者はモジュールを名札として受け取るだけで済みます。
# user.py
import order
class User:
def __init__(self, name):
self.name = name
def summary(self, data):
return f'{self.name}様: {order.format_order(data)}'
ただし、ここに落とし穴があります。この書き換えは、片方だけやっても効きません。
実際にuser.pyだけを直して動かしてみたところ、エラーは同じ文面のまま残りました。order.pyのほうがfrom user import Userを求め続けているからです。
両方のファイルをimportの形に直すと、ようやく正しく動きました。片側だけ直して直らないと悩む人が多いので、ここは覚えておく価値があります。
直し方その3:関数の中でimportする¶
輪の片側が、読み込み時ではなく実行時にしか必要ないこともあります。その場合は、import文を関数の中へ移せます。
class User:
def __init__(self, name):
self.name = name
def summary(self, data):
from order import format_order # 呼ばれたときに読み込む
return f'{self.name}様: {format_order(data)}'
summary()が呼ばれるころにはorder.pyの読み込みが終わっているので、輪になりません。これも手元で動作を確認しました。
効果は確実ですが、import文がファイルの先頭に揃わなくなります。応急処置と考えて、落ち着いたら1つめの方法へ戻すのがおすすめです。
直し方その4:型ヒントのためだけのimportを分ける¶
最近よくあるのが、型ヒントを書きたいだけなのに輪ができてしまうケースです。
型ヒントは人とツールのための情報で、処理そのものには使われません。それなら、型チェックのときだけ読み込むようにできます。
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from order import Order
class User:
def __init__(self, name):
self.name = name
def summary(self, data: Order) -> str:
return f'{self.name}様: {data.label()}'
TYPE_CHECKINGは実行中は常に偽になる変数なので、from order import Orderの行は実行されません。それでもmypyなどの型チェッカーは、この行を読んで型を理解してくれます。
なお冒頭のfrom __future__ import annotationsは、Python 3.14以降では不要です。3.14から型ヒントの評価が後回しになったためで、3.13以前も動かす必要があるときだけ付けてください。
型ヒントそのものがまだ不安な方は、こちらから始めるとつながります。【関連記事】Pythonの型ヒントとは?型ヒントの基礎を解説
4つの方法を、どう選ぶか¶
迷ったときのために、選び方を並べておきます。
| 方法 | 向いている場面 | 注意点 |
|---|---|---|
| 共通部分を切り出す | 根本から直したいとき | ファイルが1枚増える |
importの形に変える |
設計を変えたくないとき | 両側を直す必要がある |
| 関数の中でimport | すぐ動かしたいとき | import文が散らばる |
TYPE_CHECKING |
型ヒントのためだけのとき | 実行時には使えない |
上から順に試すのが基本です。時間がないときだけ3番を使い、あとで1番に戻してください。
輪を作らないための考え方¶
最後に、そもそも出さないための予防を一言だけ。
ファイルどうしの関係は、行き止まりのある一方通行にしておくのが理想です。土台になる部品ほど何も読み込まず、上の階にあるファイルだけが下を読む形になります。
分け方に迷ったら、こちらの記事が指針になります。【関連記事】Pythonのモジュールとパッケージの違いとは?フォルダ構成で迷う初心者へ
まとめ¶
循環インポートは、2つのファイルが互いを読み合い、一周したときに中身がまだ無いことで起きます。
エラー文に partially initialized module と書いてあったら、まずtracebackで同じファイル名が2回出ていないかを探してください。
直し方は4つ。共通部分を切り出す、両側をimportの形にする、関数の中でimportする、型ヒントならTYPE_CHECKINGで囲む。
いちばん手早いのは3番ですが、いちばん報われるのは1番です。エラーは設計を見直す口実だと思って、落ち着いて向き合ってみてください。
ここまでお読みいただきありがとうございました。