Last Updated: 2/6/2024, 5:44:57 AM

# 名前空間ってなに?

名前が被らないように
苗字を作るようなものです。
Hello, world!

「名前空間」とは、複数の「名前」を1つにまとめたものです。 一般にプログラミング言語で「名前」と言えば「変数」と「定数」を指しています。

例えば標準ライブラリ math の中にはたくさんの名前すなわち「変数」が保存されています。 それらを import した時に math という「名前空間」に、まとめ上げられています。

そして、変数は「属性参照」することができます。

import math
math.inft

6.3.1. 属性参照 - Python 言語リファレンス (opens new window)
属性参照は、プライマリの後ろにピリオドと名前を連ねたものです:
attributeref ::= primary "." identifier

この上の式みたいなものは EBNF と呼ばれるものです。 EBNF を使って Python の文法を定義しています。 知らなくても全然大丈夫です。

# 1. 名前

Python を使っている人に「定数」と言っても、 いまいちピンと来ないと思います。 「定数」とは代入できない「名前」のことです。

Python に、そんなものあるのでしょうか? 実はあります。  None  ,  True  ,  False  ,  NotImplemented  ,  Ellipse  ,  __debug__  , が「定数」です。 この6つしかありません。

以降  「変数」   「定数」  と まとめて、カッコつけて  「名前」  と呼ぶことにします。

「定数」は変更できません。 例えば None に代入しようとすると SyntaxError になります。 これは None などの定数が  予約語  として定義されているためです。

>>> # SyntaxError になる。
>>> None = 0
  File "<stdin>", line 1
SyntaxError: can't assign to keyword
>>>

 予約語  とは if, for, class などの既に使い方が決まっていて、  名前  として利用できない文字列のことです。

エラー文を読んで見ます。 "SyntaxError: can't assign to keyword." 日本語に訳すと「文法エラー: 予約語に代入できません。」となります。

 SyntaxError  というのが結構ポイントです。 例えば、関数を定義する時に、実行する前に、定義した段階で弾かれます。 なにが言いたいかというと、ちょっと強引に定数が実装されているということです。

>>> def f():
...   None = 1
...
  File "<stdin>", line 2
SyntaxError: can't assign to keyword
>>>

# 2. 名前空間を使う

# 2.1. たとえ話 氏と名

ここに2人の人がいて、2人とも名前が「大輔」でした。 2人の大輔さんについて話す時に、「大輔」では通じないので、 氏の「田中」、「鈴木」で呼んで区別します。

このとき田中、鈴木という名前空間を導入したと言えます。 この名前空間を導入したことによって、 2人の大輔さんを話の中で区別できるようになります。

ちなみに姓・氏・名は別物です。 この記事がちょっと面白い。 例えばキングダムに登場する嬴政の「嬴」。 これは氏ではなく姓になります。 「政」は名ですね。

夏、殷、周の時代においては「姓」と「氏」は明確に異なるものであった。 同郷(邑)の血縁集団を示す「姓」の概念が先に存在し、 社会の発展に伴って特定の社会的地位に所属する一族を表す、 より狭い血縁集団である「氏」の考え方が生まれた。

...

姓は母系を示すため、最も古い姓は部首に「女」を含む。

漢姓 - Wikipedia (opens new window)

嬴政は氏がないので、ちょっと分かりづらいですが、 楚を春秋五覇に持ち上げた荘王の名前はつぎのようなものです。 諱(いみな)とは下の名のことです。 羋の読みはビ、熊はユウと読みます。

姓は羋、氏は熊。諱は侶、または旅。
荘王 (楚) - Wikipedia (opens new window)

# 2.2. import するとき

import math

def sqrt(n):
    print(n * "スクルト, ")

math.sqrt(2)
sqrt(2)
>>> math.sqrt(2)
1.4142135623730951
>>> sqrt(2)
スクルト, スクルト,
>>>

# 2.3. インスタンス変数を参照するとき

Python では、インスタンス変数、クラス変数を参照する時は、 名前空間を明示しないといけない仕様になっています。いまいち何言ってるんだ?って感じですよね。

例えば Python を習いたての時にやってしまいがちなのが self の書き忘れです。 もちろん Python を使い間違えたと言えばそれまでなのですが、 これがどういう間違いかと言えば「名前空間」を使わなかったということです。

class User:
    def __init__(self, name, age):
        self.name = name
        age = age  # <- self を明示しない。

user = User('山田太郎', 20)
user.name
user.age  # AttributeError
>>> user.name
'山田太郎'
>>> user.age  # AttributeError
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'User' object has no attribute 'age'
>>>

オブジェクト指向とは、 クラスを通して self という名前空間を適切に分割していく作業じゃないかなと感じています。

# 3. 名前空間を使わない

さらに PEP 20 で名前空間について言及された箇所があります。 もっと使っていきましょうと言われても、これはどういう意味でしょうか? 逆に使わなかったらどうなるのでしょうか?

名前空間は、素晴らしいアイディアです。-- もっとこれを使っていきましょう。
Namespaces are one honking great idea -- let's do more of those!
The Zen of Python - PEP 20 (opens new window)

なぜ名前空間を使うことが素晴らしいのでしょうか?  名前空間.属性  なんて書くのは、あまりに煩雑ではないでしょうか? 逆に名前空間を使わないことについて考えて見たいと思います。

# 3.1. 名前衝突

Python では基本的に import して 名前空間を使うスタイルなので意識することはありませんが、 同じ名前が定義されていた場合に重複してしまいます。

このような事象を難しい言葉で  名前衝突  と呼ばれたりします。 名前空間があるとわかりやすいですよねとかよりも、 この名前の衝突を避けるために名前空間が 導入されたという経緯があります。

プログラミング等で、 ある名前が他の個所で定義した名前と重複してしまい(二重定義)、 処理が行なえなくなってしまうこと。
名前衝突 - 通信用語の基礎知識 (opens new window)

# 3.2. Python の場合

Python の場合、PEP 8 でちゃんと名前空間を使うように指示されています。 機械で解釈させるにしても、人間が読むにしても、名前空間を明示しないとわからないだろう、という話らしいです。 ひとつ目の具体例は PEP 8 で簡単に言及されています。

#
# 1) OK
#
import math
print(math.pi)       # 名前空間を使って pi を参照

#
# 2 ) OK
#   名前空間を
#   使ってないけど
#   これも OK
#
from math import pi  # 名前空間を明示してから
print(pi)            # 名前空間を使わずに参照

#
# 3) NG
#   動くけど PEP 8 的には
#   これは NG
#
from math import *   # 名前空間を明示しないし
print(pi)            # 使いもしない

ワイルドカードを使った import (from モジュール名 import *) は、避けなければなりません。 何故なら、どの変数が import した 名前空間 に存在するか不明瞭になり、 コードを読む人間やコードを解析する自動化ツールを混乱させるからです。
Wildcard imports (from <module> import *) should be avoided, as they make it unclear which names are present in the namespace, confusing both readers and many automated tools.
Style Guide for Python Code - PEP 8 (opens new window)

逆に言えば「名前空間を使わない」とは、 「ある名前空間の配下にある名前を local スコープまたは global スコープに、そのまま叩き込む」行為だと言えます。

問題

この中で PEP 8 で最も推奨されていない import の記述方法は、どれですか?

# 3.3. C 言語の場合

Python ではある意味名前空間があるのが当たり前です。 では、何故こんなにありがたがって名前空間という言葉を使っているのでしょうか? それは Python の先輩にあたる言語、例えば C 言語の場合、 基本的には import しても、名前空間を明示することができません。

stdio というライブラリを import しても stdio.printf ではなく printf で参照します。 大学生の時にこの include の説明を「おまじない」と説明されて、 逆に「おまじない」という言葉の意味が分からなくて混乱したのを覚えています笑 いまにして思えば、そんなに難しいものではないのだから、さっさと説明してよと思います。

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

C++ の名前空間の機能は C 言語にはありません。 C 言語の標準規格にも「名前空間」という用語が出てきますが、まったく別の意味です。 すなわち、識別子は次に挙げるどれかの名前空間に属し、 異なる名前空間どうしであれば、同じ名前をつけても別物として扱われるというものです。
第 1 回  C++にあって C 言語にない機能 (opens new window)

# 4. 命名規則で名前空間を作る

ドット . を使わない場合、例えば命名規則を用いて 名前空間を生成する方法をよく見かけます。 命名規則とは言ってもドット . の代わりにアンダーバー _ を使っている程度の 話ですが。 例えば 名前空間.名前 ではなく 名前空間_名前 だということです。

# 4.1. Python の特殊属性, 特殊メソッド

実は Python でも同じ方法がとられている箇所があります。 __init__ メソッドで使われています。 prefix, suffix にそれぞれ 二重のアンダーバー __ で囲まれた名前は、 Python のインタープリタが使う専用の名前空間です。 これについては、最後に 「継承よりも合成」 でご説明させていただきます。

# 4.2. C 言語で組まれた大規模システム

大規模なシステムなどで prefix として とても長い文字列を与えられた関数が定義されているのを 官公庁向けのシステムで見たことがあります。 それはとても悲劇的なもので、関わったら負けだなと思いました。

# 4.3. CSS の BEM

また CSS の命名規則で BEM というものがあります (以下、個人の趣味で書きました、適当に読み飛ばしてください)。

リンク先の動画 6:38~ がわかりやすいです。

BEMは最もメジャーなCSS設計ルールです。
マークアップガイド - CAMO (opens new window)

BEM は パーツごとに prefix を与える命名規則です。 HTML はメニューとか表とかいくつかのパーツごとに構成されています。

メリットは、パーツごとの名前空間を作り名前衝突を避けます。 デメリットは、名前空間を作る分、名前が長くなります。

BEM は Block, Element, Modifier の頭文字です。 Block は名前空間, Element は名前に該当するかなと思っています。 名前空間__名前_補足 みたいな感じですかね。

/*
 *  __ ... Blocl   と Element  の区切りにはアンダーバー2つ
 *  _  ... Element と Modifier の区切りにはアンダーバー1つ
 *  -  ... 名前を区切りたいときは - ハイフン
 */
.block__element_modifier {
  font-size: 17px;
}

.block-name__element-name_modifier-name {
  font-size: 17px;
}

基本的にこの3層構造のみで、Block をネストさせたり、 Element をネストさせるのも NG らしいです。 つまり 名前空間.名前 の構造だけが許されています。

なぜ、いけないのかというと、 「詳細度の高い」 CSS になってしまうからです。 「詳細度が高い」というのは位置を細かく指定してしまっているということです。

例えば p.mystyle.mystyle よりも 「詳細度が高い」と言えます。 何故なら p.mystyle は p タグでかつクラスが mystyle と指定されているのに対して .mystyle はクラスが mystyle であるものすべてに適用されるからです。

p.mystyle {
  font-size: 17px;
}

.mystyle {
  font-size: 17px;
}

最初は PEP 20 の Explicit is better than implicit. の精神で ガチガチに固め方がいいやろと思って詳細度高めに書こうとしていたのですが。

実際にやってみると確かに結構頻繁に HTML の要素の構造を あーでもない、こーでもないと、いじるので。

ガチガチに位置を固定させてしまうと、 もう他の箇所に HTML の要素を移動させることができなくなってしまいます。 詳細度の高い CSS だと、もう煩雑過ぎてどうにもならなくなってしまうのです。

デザインの世界のアプローチと コーディングの世界のアプローチってなんか違うんだなってなんとなく思いました。

# 5. なんでいまさら名前空間?

名前(アセンブラ言語) -> 名前空間(オブジェクト指向言語) -> 再代入の禁止(関数型言語) という流れがあると感じたからです。

  1. アセンブラ ... 名前
  2. オブジェクト指向 ... 名前空間
  3. 関数型プログラミング ... 名前の再代入の禁止

# 5.1. なんで?

名前空間は、オブジェクト指向で重要だと思ったからです。

機械語からアセンブラになることでデータに「名前」を付けられるようになりました。 そして高級言語になることで、 大抵の言語では構造体を得て、データは「名前空間」を得ることができました。

オブジェクト指向がやりたいと思った時に、 便利であるかどうかは別にして、 最低限、「名前空間」さえあればできてしまいます。

そして再代入させることは便利なことです。 再代入を許してしまうと、一瞬便利そうですが、 コードを後から読む人間には辛いコードになってしまいます。

# 5.2. どうやって?

まず 「import ってなに?」 で名前空間に触れます。 次に 「スコープってなに?」 で見えない名前空間であるスコープに触れます。 そして「オブジェクト指向ってなに?」 でオブジェクト指向は、 名前空間を適切にわけるということに触れます。

最近は、継承を避ける傾向にあるらしいです。 なぜなら継承は適切にわけた名前空間をくっつけてしまう可能性があるからです。 そのことを 「継承より合成なのか?」 で見ていきます。

また再代入が危険であることを「副作用ってなに?」 で見て、 最終的に「map, filter ってなに?」 で、 最終的に関数型プログラミングの入り口にまで理解をつなげていきます。

import はあまり概念的な話よりも、機能的な話に終始してしまいます。 これは import よく使う機能であるにも関わらず、多くの方が引っかかっていると感じたからです。