プライベートもどきなインスタンス変数

入門Python3 6.9


この記事でprivateなインスタンス変数は定義できないと書きましたが、変数の先頭にアンダーバーを二個付けることでprivateに近い変数を定義できるということです。

class Foo():
    def __init__(self,str):
        self.__name = str

foo = Foo("str")
print(foo.__name) # ここで例外
$ py main.py
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    print(foo.__name) # ここで例外
AttributeError: 'Foo' object has no attribute '__name'

外から「__name」にアクセスしようとしたら例外が発生しました。


さてprivateに近いと書きましたが、つまりそれは仕様を把握していれば外からでもアクセスができるということです。

「_クラス名__インスタンス名」で外からでもアクセス可能となります。

class Foo():
    def __init__(self,str):
        self.__name = str

foo = Foo("str")
print(foo._Foo__name) # アクセスできる
$ py main.py
str


ではグローバルスコープで定義した場合はどうなるのか気になったので試してみました。

class Foo():
    pass

foo = Foo()
foo.__name = "str"
print(foo.__name)
$ py main.py
str

この場合は例外にならず動作します。


このことからあくまでもインスタンス変数を定義した時のスコープ内のクラス名が使われるということみたいです。

その証拠にグローバルスコープで定義した変数はクラス内のスコープからは参照できません。

class Foo():
    def print_name(self):
        print(self.__name) # ここで例外

foo = Foo()
foo.__name = "str"
print(foo.print_name())
$ py main.py
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    print(foo.print_name())
  File "main.py", line 5, in print_name
    print(self.__name) # ここで例外
AttributeError: 'Foo' object has no attribute '_Foo__name'

このように例外の内容をみると_Foo__nameが存在しないと表示されてますね。


同じ理由から継承関係においても基底クラスで定義されたインスタンス変数は派生クラスから参照できません。

class Foo():
    def __init__(self,str):
        self.__name = str # _Foo__nameが定義される
    
class Bar(Foo):
    def __init__(self,str):
        super().__init__(str)
    
    def print_name(self):
        print(self.__name) # _Bar__nameを参照し例外

bar = Bar("str")
bar.print_name()
$ py main.py
Traceback (most recent call last):
  File "main.py", line 17, in <module>
    bar.print_name()
  File "main.py", line 14, in print_name
    print(self.__name) # _Bar__nameを参照し例外
AttributeError: 'Bar' object has no attribute '_Bar__name'

この仕様によりもしBar側で__nameを定義しても_Bar__nameが定義されるので基底クラスの変数と競合する可能性はないのでうまいこと辻褄あってていいですね。