カプセル化
オブジェクト指向プログラミングの特徴的な要素の1つにカプセル化(encapsulation)という概念があります。カプセル化とは、データを外部から隠蔽して見えないようにすることです。
Python ではクラスからインスタンスが作られるときに、インスタンス変数(データ属性)に値が渡されます。しかし、このインスタンス変数はあとで簡単に書き換えることもできるのです。サンプルコードを使って具体例を見てみましょう。
# PYTHON_CLASS_ENCAPSULATION_01 class MyDic: def __init__(self, *x): # 空白のディクショナリを用意 self.dic = {} # 渡された引数にキーを割り当ててディクショナリに格納 for ct in range(len(x)): self.dic[ct] = x[ct] # MyDicクラスのインスタンスを生成 my_color = MyDic("red", "blue", "green") # ディクショナリの内容を表示 print("変更前の内容:", my_color.dic) # ディクショナリの内容を変更 my_color.dic[1] = "yellow" print("変更後の内容:", my_color.dic)
変更前の内容: {0: 'red', 1: 'blue', 2: 'green'} 変更後の内容: {0: 'red', 1: 'yellow', 2: 'green'}
MyDicクラスは、受け取った引数に 0 から順に数字の key を割り当てながらディクショナリに収めていきます。アスタリスク (*) のついた引数は可変引数といって、任意の数の引数を渡すことができます。my_color というオブジェクトは生成時に
{0: 'red', 1: 'blue', 2: 'green'}
というインスタンス変数をもっていますが、
my_color.dic[1] = "yellow"
という記述でインスタンス変数にアクセスして、データを書き換えることができます:
{0: 'red', 1: 'yellow', 2: 'green'}
このように、オブジェクトの中身を自在に変えられるという設計はプログラミングに柔軟性を与える一方、デメリットにもなりえます。一般に、インスタンス変数はオブジェクトのメソッドに使用されるので、インスタンス変数に変更が加えられると、メソッドの動作も影響を受けるからです。Pythonのプログラマーは「このインスタンス変数は書き換えてほしくない」と思う場面がしばしばあります。そこで、冒頭で述べたように、カプセル化の要素のひとつであるデータの隠蔽が必要となるのです。
慣例的に書き換えを禁止します
Pythonコミュニティでは「アンダースコアで始まる変数は書き換えてはいけない」というルールが存在します。わざわざ自分の作ったプログラムを壊したいと思う人はいないはずですから、普通はこのルールを守ります。MyDicクラスの self.dic を書き換えてほしくないと思えば、self._dic のように記述しておきます。
アクセスを制限します
そのような慣例に頼るのが不安だと考えるならば、変数名の先頭にアンダースコアを 2 つ付けて強制的にアクセスを禁止することもできます。MyDicクラスのインスタンス変数 self.dic を外部から隠蔽してみましょう。
# PYTHON_CLASS_ENCAPSULATION_02 class MyDic: def __init__(self, *x): # 空白のディクショナリを用意 self.__dic = {} # 渡された引数にキーを割り当ててディクショナリに格納 for ct in range(len(x)): self.__dic[ct] = x[ct] # MyDicクラスのインスタンスを生成 my_color = MyDic("red", "blue", "green") #ディクショナリの内容を変更 my_color.__dic[1] = "yellow"
このコードを実行すると AttributeError が発生し、’MyDic’ object has no attribute ‘__dic’というメッセージが表示されます。「MyDicクラスのオブジェクトには __dic という変数は存在しません」という意味です。これだと、本当はあるのに嘘をつくことによって隠蔽しているように思えますが、そうではなく、変数名の先頭にアンダースコアを 2 つ添えると、内部で自動的に名前を書き換えるので、外部から __dic という名前ではアクセスできなくなっているのです。ただし、クラス内部で元の名前を使うと、それも自動的に置き換えてくれるので、元の変数名でメソッドを記述することに差し支えありません。
メソッドを通してデータを参照します
上のサンプルコードでは、外部からのインスタンス変数へのアクセスが禁止されているために、オブジェクトのデータを print()関数で表示することもできません。そこで、データを表示するためのメソッドを追加しておきます。
# PYTHON_CLASS_ENCAPSULATION_03 class MyDic: def __init__(self, *x): # 空白のディクショナリを用意 self.__dic = {} # 渡された引数にキーを割り当ててディクショナリに格納 for ct in range(len(x)): self.__dic[ct] = x[ct] def show(self): return self.__dic # MyDicクラスのインスタンスを生成 my_color = MyDic("red", "blue", "green") # my_color.__dicを表示 my_color.show()
{0: 'red', 1: 'blue', 2: 'green'}
このコードには self.__dic のデータを表示する show()メソッドが追加されています。このように、インスタンス変数に直接アクセスするのではなく、メソッドを通して参照できるようにしておけば外部からの書き換えを防ぐことができます。ただし、実はそれでも隠蔽は完全であるとはいえません。内部での変数名の書き換えの法則を知っていれば、やはり外部からアクセスできてしまうからです。そうした意味で Python のデータ隠蔽機能は C++ や Java など、他のオブジェクト指向言語に比べると簡素なものですが、それはプログラミングの柔軟性と引き換えにした欠点なのです。
コメント
「カプセル化」の説明で、[1]はクラス、[2]がカプセル化の役割だと思うので、ここでは、「カプセル化はデータを外部から隠蔽して見えないようにすることです。」としたほうが、わかりやすいと思いました。
言われてみれば、確かにそうですね。(^^)
説明を書き換えておきました。
いつも気づきにくい点をご指摘くださって、ありがとうございます。