【Python】json.pyという名前でimportが壊れる?モジュール名の衝突を調べる

PythonのTopに戻る


jsonをimportしたのにloadsが見つからないときは、自分のjson.pyを読み込んでいないか確認する。Pythonは検索経路の順にモジュールを探すため、スクリプトと同じ場所にあるファイルが標準ライブラリより先に見つかることがある。解決の基本は、自作ファイルを用途の分かる別名へ変更し、新しいPythonプロセスで読み直すことである。

インストールでは直らない名前の衝突

標準ライブラリのjsonは、通常のPythonに含まれている。json.loadsがないというだけで、追加のjsonパッケージをインストールする必要はない。読み込んだ対象が違う場合は、何を追加してもその手前にある自作ファイルが優先され続けるからである。

同じ問題はrandom.py、csv.py、typing.pyなどでも起こり得る。また、自作のnumpy.pyやpandas.pyが外部ライブラリを隠してしまう場合もある。ファイルだけでなく、同名のパッケージディレクトリにも注意したい。重要なのは名前の見た目ではなく、実際にどこから読み込まれたかである。

一時フォルダーで衝突を再現する

次の例は一時ディレクトリ内だけにjson.pyを作り、別のPythonプロセスで読み込む。その後、練習用ファイルをjson_notes.pyへ変更してもう一度読み込む。既存のプロジェクト内のファイルを削除・変更する例ではないので、そのまま試せる。

example.py

from pathlib import Path
from tempfile import TemporaryDirectory
import os
import subprocess
import sys

probe = """
from pathlib import Path
import json
print(Path(json.__file__).name)
print(hasattr(json, "loads"))
"""
env = os.environ.copy()
env.pop("PYTHONPATH", None)
env["PYTHONDONTWRITEBYTECODE"] = "1"
with TemporaryDirectory() as folder:
    root = Path(folder)
    shadow = root / "json.py"
    shadow.write_text('note = "my notes"\n', encoding="utf-8")
    first = subprocess.run(
        [sys.executable, "-c", probe], cwd=root, env=env,
        check=True, capture_output=True, text=True,
    ).stdout
    print("Before rename:")
    print(first, end="")
    assert first == "json.py\nFalse\n"

    shadow.rename(root / "json_notes.py")
    second = subprocess.run(
        [sys.executable, "-c", probe], cwd=root, env=env,
        check=True, capture_output=True, text=True,
    ).stdout
    print("After rename:")
    print(second, end="")
    assert second == "__init__.py\nTrue\n"

実行結果

Before rename:
json.py
False
After rename:
__init__.py
True

最初はjson.pyというファイルが読み込まれ、loadsが存在しない。改名後に起動したPythonでは、標準ライブラリのjsonパッケージの__init__.pyが使われ、loadsを呼び出せる。出力は比較に必要な末尾のファイル名だけに絞っている。

本当の作業場所で読み込み元を調べる

自分のコードを調べる場合は、import jsonの直後にprint(json.__file__)を置く。一般的なファイル由来のモジュールなら読み込み元を表示できる。ただし組み込みモジュールなど、__file__を持たないものもあるため、すべてのモジュールへ同じ確認を使えるわけではない。

表示された場所が標準ライブラリではなく自分の作業フォルダーなら、そのファイル名や配置を確認する。ファイルの中で再びimport jsonと書いている場合は、自分自身を初期化の途中で参照してしまい、partially initializedといった説明を含むエラーになることもある。

このとき、sys.pathの先頭を削る応急処置を入れると、別の自作モジュールが読み込めなくなるなど、新しい問題が起こりやすい。標準ライブラリ名を避けるように改名し、呼び出し側のimportも必要に応じて修正する方が構造を保ちやすい。

改名後は新しいプロセスで確認する

Pythonは読み込んだモジュールをsys.modulesに保持する。実行中にファイル名だけ変えても、同じプロセスから再度importしたときに古いモジュールが使われることがある。ターミナルなら終了して再起動し、ノートブックならカーネルを再起動して確かめる。

__pycache__内の通常のキャッシュがあるだけで、元の.pyを改名した後も必ず衝突が続くわけではない。原因を確認せずプロジェクト中のファイルを一括削除する必要はない。古い配置や独立した.pycが残っている場合などは、読み込み元を見て対象を絞って調べる。

名前を付ける段階で予防する

単に扱う形式をファイル名にするより、read_measurements.pyやjson_report.pyのように目的を含めると衝突しにくい。チュートリアルで使った名前をそのまま保存するときも、標準ライブラリや利用中パッケージと同じになっていないかを見ておこう。

改名でimportが通ったら、実際に使う関数を一つ呼んで確認する。名前の衝突と、バージョン違いによるAPI変更が同時に存在する場合もある。読み込み先が想定どおりであることと、そのライブラリが必要な機能を持つことは、順に確かめると混乱しにくい。

動作確認と参考資料

掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。

関連するTips


PythonのTopに戻る