「名前空間ん? なんじゃそら」ってのも今となっては昔の話だ。オライリーを読んだらだいぶん理解できたと思う。でもちょっと調べなおしていたら、PHPには namespace なる宣言が存在することを知った。pythonにそういう構文があることは知らないぞ? ってことは俺、pythonにおける名前空間に関して何か見逃していることがあるのでは!? ……と危機感を覚えて調査してみた話。

以下のようなシチュエーションを想定すると、名前空間の重要性がわかりやすいかもしれん。

  • とっても便利なモジュールをふたつ見つけた
  • 両方使いたいのだが、なんとそいつらは名前が全く同じ
  • これじゃあスクリプト内で同時に使うことができん。どっちかが上書きされちゃうからな
  • でも併用したいの!
  • だったらモジュール名自体はそのままにして、それぞれにアダ名をつけて管理しようぜ!

まずはPHPの namespace について。同名のファイルは同じディレクトリに置けないから、 fromAsan fromBsan (Aさんからもらった便利モジュールと、Bさんからもらった便利モジュールって意味)というディレクトリにわけておいておくとする。

main
  ├─ main.php
  ├─ fromAsan
  │    └─ superBenriTool.php
  └─ fromBsan
       └─ superBenriTool.php

こんな感じにね! そんでmain.phpの中でふたつのsuperBenriToolを使いたいわけなんだけど、そこで namespace が登場する。これは読み込まれる側であるsuperBenriTool.phpたちに書く。

# fromAsan/superBenriTool.php

<?php
namespace A;

function superBenriFunc() {
    echo "なんかすごい処理Aが発生した!";
}
# fromBsan/superBenriTool.php

<?php
namespace B;

function superBenriFunc() {
    echo "なんかすごい処理Bが発生した!";
}

するとmain.phpでは、同名のファイルを複数読み込むことができるようになるのだよ。

# main.php

<?php

include "fromAsan/superBenriTool.php";
include "fromBsan/superBenriTool.php";

A\superBenriFunc();
B\superBenriFunc();
# main.php の実行結果
なんかすごい処理Aが発生した!なんかすごい処理Bが発生した!

やった! phpではnamespace宣言をすることで、同名のファイルをいくらでもひとつのファイル内で使うことができるんだ! まあモジュールファイルのほうにnamespaceを書かないといけないからちょっと面倒だけどね! すごいぞ!

……いやマテ、

# main.py

import fromAsan.superBenriTool as A
import fromBsan.superBenriTool as B

A.superBenriFunc()
B.superBenriFunc()

それってpythonがasでサラッとやってることじゃねーか! というわけでpythonではPHPのnamespace宣言と同じようなことがメインファイルだけで行える。冒頭の「俺、pythonにおける名前空間に関して何か見逃していることがあるのでは!?」という危機感は杞憂だったってことだ。

上の例では各モジュールを相対パスで読み込んでいるので、どっちのsuperBenriToolがfromAsanでどっちのsuperBenriToolがfromBsanなのかすぐわかる。ただ俺の場合は、「奥義:相対インポートなんていーらない」(コンソールプログラムのexe化とかで触れてる)をよく使っているんだ。これは sys.path にモジュールの入ってるディレクトリを自動で全登録し、いちいち相対インポートの書き方をしなくてもサクッとインポートが行えるようにする技だ。すわ奥義を使ったら今回のような状況に対応できないのではと思ったが……

# ==============================
# 奥義:相対インポートなんていーらない
# ==============================
def makeDirs():
    dirs = []
    dirs.append(os.getcwd())
    for directory in dirs:
        files = os.listdir(directory)
        for f in files:
            path = directory + "\\" + f
            if os.path.isdir(path):
                dirs.append(path)
    return dirs

def makePaths():
    dirs = makeDirs()
    for directory in dirs:
        if directory in sys.path:
            pass
        else:
            sys.path.insert(0, directory)

makePaths()

# 奥義ここまで

import fromAsan.superBenriTool as A
import fromBsan.superBenriTool as B
A.superBenriFunc()
B.superBenriFunc()

それも杞憂だった。いや、そりゃそうだよな。sys.pathに登録したディレクトリだからって相対インポートでパス指定できなくなるわけじゃないんだから、しっかりディレクトリ指定してインポートしたいモジュールだけしっかりディレクトリ指定すればよいのだ。こういう右往左往するたびに、おそらく俺は四角い車輪の再発明みたいなことをしてるんだろうなーと思ったりするが、まあその、我流ってちょっとカックイイし、他者の技術を受け入れられる柔軟性さえ持ち続けていれば問題ないと考え看過している。