Edited at

Pythonの疑似private変数とクラス継承によるトラブル

More than 1 year has passed since last update.

Pythonのクラスにはメンバ変数のアクセス制限という概念がありません。全ての変数がpublicとして扱われます。

そこで、疑似private変数を作れるようになっているのですが、これをクラス継承と併用しようとするとバチこいたためトラブルシューティングとしてメモ。


error_case

class BaseClass(object):

def __init__(self):
self.__param = "I'm BaseClass"

class ChildClass(BaseClass):
def hello(self):
print self.__param

ChildClass().hello()



AttributeError                            Traceback (most recent call last)

<ipython-input-7-898f72a5b39a> in <module>()
----> 1 ChildClass().hello()
<ipython-input-6-9e0be3f6ef4d> in hello(self)
5 class ChildClass(BaseClass):
6 def hello(self):
----> 7 print self.__param
AttributeError: 'ChildClass' object has no attribute '_ChildClass__param'


疑似private変数

メンバ変数の名前の頭にアンダースコアを二つつけると、疑似的にprivate変数を作ることができる。

しかし内部的には、_{classname}__{変数名}という名前のpublic変数を用意して、クラス内からは__{変数名}という名前で参照できるようにしているだけ。それが「疑似」private変数である所以。


sample_program

class Sample(object):

def __init__(self):
self.__param = "I'm Sample"

sample = Sample()
print sample.__param



AttributeError                            Traceback (most recent call last)

<ipython-input-8-f5d0d60ad401> in <module>()
4
5 sample = Sample()
----> 6 print sample.__param
AttributeError: 'Sample' object has no attribute '__param'


sample_program

sample = Sample()

print sample._Sample__param


"I'm Sample"



クラス継承との組み合わせ

一番上のエラーケースのように、 親クラス内のメソッドで設定した疑似private変数に、子クラスの内部からアクセスしようとすると失敗する

どうやら原因は疑似private変数の内部仕様による。

親クラス内で宣言した疑似メンバ変数は、それが継承されていようと、内部的にはself._BaseClass__paramに突っ込まれるらしい。

一方、子クラスからself.__paramを見ようとすると参照されるのは当然_ChildClass__paramなので、そんな変数ないよというエラーが帰ってきてしまうというカラクリ。


解決策

クラス継承を使うなら、親クラス内で一緒にゲッタを宣言しておく。


successful_case1

class BaseClass(object):

def __init__(self):
self.__param = "I'm BaseClass"

def get_param(self):
return self.__param

class ChildClass(BaseClass):
def hello(self):
print self.get_param()



"I'm BaseClass"


(更新) 親クラスのプロパティにする。外部からの直接のセットを防ぐことができる。


successful_case2

class BaseClass(object):

def __init__(self):
self.__param = "I'm BaseClass"

@property
def _param(self):
return self.__param

class ChildClass(BaseClass):
def hello(self):
print self._param


滅多に起きないシチュエーションかもしれないが意外と不便。


version

2.7.12 |Anaconda 4.2.0 (x86_64)| (default, Jul 2 2016, 17:43:17) \n[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)]