sec05 - Appendix 2:Pythonのモジュール名が衝突する原因と解決方法:標準ライブラリとの名前重複を防ぐポイント
スポンサーリンク

Pythonのモジュール名が衝突すると何が起きるのか

Pythonを学んでいると、少しずつ「外部モジュール」や「ライブラリ」を使うようになります。これらを使うと、標準機能ではできないことが簡単に実現できるようになります。 しかし、ここで注意すべきことがあります。それは「モジュール名や関数名の衝突(名前の衝突)」です。

Pythonでは、「同じ名前」をうっかり使ってしまうと、思わぬバグが発生する可能性があります。

たとえば、Pythonには標準ライブラリのrandomモジュールが用意されています。乱数を扱う際にとてもよく使われるものです。 もし、あなたが自分で作ったrandom.pyを使いたい場合、標準ライブラリのrandomと自作のrandomはどのように扱われるのでしょうか。確認してみましょう。

Pythonで名前衝突が起きる具体例

次のような構成を想定してみます。

project/
├─ random.py
└─ main.py

そして、自作のrandom.pyの中身を次のようにしてみましょう。

# random.py(自作ファイル)
def hello():
    print('これは自作randomモジュールです')

続いて、main.pyの中で、randomを使おうとしてみます。

# main.py
import random

print(random)
print(random.randint(1, 10))  # 標準ライブラリの関数
print(random.hello())  # 自作ライブラリの関数

VSCodeで発生するモジュール衝突の挙動

さて、このコードをVisualStudioCodeで実行するとどうなるでしょうか? 結果は標準ライブラリのrandomが読み込まれます。次の結果のように、randomはPythonが保存されているパスを参照しているのが確認できます。そして、標準ライブラリのrandomに用意されているrandom.randint()関数が実行され、その結果が得られます。自作のrandomは読み込まれていないので、random.hello()(自作randomの関数)を実行しようとするとAttributeErrorが発生します。

<module 'random' from 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\random.py'>
4  # <- ランダムなので、実行するたびに結果は異なります。

AttributeError: module 'random' has no attribute 'hello'

コマンドラインでのimport挙動

VisualStudioCodeで実行すると、標準ライブラリのrandomが優先的に参照されますが、コマンドラインの場合は結果が異なります。

コマンドラインでPythonファイルを実行するには、まずはカレントフォルダの指定を、cdコマンドを使って行います。(パスの指定は、皆さんの環境に合わせてください。)

cd C:\Users\xxx\Documents\python101\project

pythonコマンドを使って、main.pyを実行します。

python main.py

出力結果は次のようになります。

<module 'random' from 'C:\\Users\\xxx\\Documents\\python101\\project\\random.py'>
Traceback (most recent call last):
  File "C:\Users\xxx\Documents\python101\project\main.py", line 5, in <module>
    print(random.randint(1, 10))  # 標準ライブラリの関数
          ^^^^^^^^^^^^^^
AttributeError: module 'random' has no attribute 'randint'
(consider renaming 'C:\Users\xxx\Documents\python101\project\random.py' since
it has the same name as the standard library module named 'random'
and prevents importing that standard library module)

この出力結果を見てみると、参照されているのは自作のrandomモジュールです。そして、自作モジュールにはrandint()関数が存在しないため、エラーが発生しています。

main.pyの5行目のprint(random.randint(1, 10))をコメントアウトして実行すると、エラー無く実行されます。

スポンサーリンク

Pythonのimport処理とモジュール検索パスの仕組み

コマンドラインの挙動 (sys.pathによる検索順序の解説)

Pythonがimportされたときに探す順序は、「モジュール検索パス(sys.path)」と呼ばれるlistに基づいています。 このlistの最初の要素は、現在のスクリプトがあるディレクトリ(カレントディレクトリ)になります。ですので、検索パスのルールに従い、自作したrandom.pyimportされます。

import sys
from pprint import pprint

pprint(sys.path)

Pythonはこの検索パスのルールに基づいて実行されるため、コマンドラインの挙動が正しいです。

VSCodeのデバッガによるimport優先順位の違い

では、なぜVisualStudioCodeから実行した場合は、標準ライブラリのrandomが優先されたのでしょうか。

実は、Pythonはsys.modulesというdictのデータに、モジュールの名前と参照先の情報を持っています。main.pyの中で何かモジュールをimportしようとした時に、同じ名前のモジュールがsys.modulesに既に登録されていると、Pythonは改めてモジュールの検索をせずに、登録されているモジュールを使用します。

VisualStudioCodeからmain.pyを実行すると、読み込まれる優先順位が低いはずの標準ライブラリが、自作プロジェクトのモジュールよりも先に読み込まれてしまいます。つまり、標準ライブラリのrandomモジュールが、main.py内のimport randomが処理される前にsys.modulesに登録されるため、自作のrandomモジュールは読み込まれません。(VisualStudioCodeから実行した時に、先に標準ライブラリが読み込まれてしまうのは、VisualStudioCodeのデバッガ設定によるものと考えられます。)

ここでは、実行環境によって、参照先が変わってしまう危険性があることを覚えておいてください。

sysやosモジュールの場合

ビルトインのsysモジュールは、Pythonの実行環境自体(インタープリタの動作、標準入出力、モジュール検索パスなど)を制御するコア中のコアのモジュールです。そのため、sysはコマンドラインからの実行であっても、自作のsysモジュールは読み込むことはできません。

他にも、自作モジュールよりもビルトインの方が優先されるモジュールが存在します。(osモジュールなど。) ビルトイン モジュールと名前が衝突して、予期せぬ挙動が発生しないように注意する必要があります。

モジュール名の衝突を防ぐための具体的な対策

標準ライブラリには、実行環境によって優先度が変わるもの(例:randomなど)や、Pythonのコアモジュールであるため最優先されるもの(例:sysosなど)があります。いずれの場合であっても、この問題を防ぐためには、次のような対策をする必要があります。

  • 標準ライブラリ/ビルトインモジュールと同じ名前のファイルを作らない
    • random.py, sys.py, os.py, json.pyなどの名前は避ける。
    • どうしてもモジュール名に"random"という単語を使いたい場合は、"random_tools"のように被らない名前に変更しましょう。
  • フォルダ分けする(別名のパッケージにする)
    • import rand.randomimport utility.randomのように、自作パッケージの階層の中にrandom.pyを作り、import時に標準ライブラリと区別できるようにします。
    • パッケージ化の際、フォルダ名をrandomとすると、インポート時に名前が衝突してしまいます。randutilityのように、名前が衝突しないようなディレクトリ名にしましょう。

Pythonのモジュール名衝突の要点まとめ

  • 標準ライブラリ/ビルトインと同名のファイルを作ると、想定外のエラーが起きる。
  • 不要な衝突を避けるため、モジュール名は慎重に決めよう。
スポンサーリンク