1
2

More than 1 year has passed since last update.

【Python3】プライベートなインスタンス変数にクラス外からアクセスできてしまう

Posted at

Pythonのカプセル化について備忘録としてまとめます。

環境

  • Ubuntu20.04
  • Python 3.8.10

筆者の認識

  • インスタンス変数の頭にアンダーバーを2つ着けるとプライベート変数として扱われ、クラスの外部から参照・再代入できなくなる。
Hoge.py

Class Hoge(object):
  def __init__(self, hoge: str):
    # 外部からアクセスできなくする
    self__hoge = hoge


hoge = Hoge("fuga")
# エラーになるはず
hoge.__hoge = "foo"
# エラーになるはず
print(hoge.__hoge)

実際の挙動

Hoge.py

class Hoge(object):
  def __init__(self, hoge: str):
    self__hoge = hoge


hoge = Hoge("fuga")

hoge.__hoge = "foo"

print(hoge.__hoge)

Hoge.pyをコマンドライン上で実行すると再代入・変数へのアクセスともにできてしまいます。

$ python3 Hoge.py 
foo

再代入せず、参照だけした場合、エラーになりました。(意図した挙動)
参照はできないが代入はできるというのが不可解です。再代入したら参照もできてしまうのもよくわかりません。

Hoge2.py

class Hoge2(object):
  def __init__(self, hoge: str):
    self__hoge = hoge

hoge = Hoge("fuga")
print(hoge.__hoge)
$ python3 Hoge2.py 
Traceback (most recent call last):
  File "Hoge2.py", line 9, in <module>
    print(hoge.__hoge)
AttributeError: 'Hoge2' object has no attribute '__hoge'

検証

1. 本当に変数名の頭にアンダースコアを2つ付けるとプライベート変数になるのか

Pythonの公式ドキュメントには下記の記述がありました。

“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python.

どうやら一般的な意味でのプライベート変数はPythonには無いようです。( 同じページに Python classes provide all the standard features of Object Oriented Programming なんて書いてるのは何なんだ)

なんで、「変数名の頭にアンダースコアを2つつけると(厳密な意味での)プライベート変数になる」 というのは偽であると言えそうです。

Pythonのマングリング(アンダースコア2つから始まる変数の名前を置換する機能)によりこのような挙動になります。

例: Hogeクラスの __hoge 変数は _Hoge__hoge という変数名になる

この機能は本来は継承元と継承先で変数名が重複しないようにするのが目的のようです。

hoge3.py

class Hoge3(object):
  def __init__(self, hoge: str):
    self.__hoge = hoge


hoge = Hoge3("fuga")

hoge.__hoge = "foo"

print(vars(hoge))

上記のスクリプトを実行すると以下のような結果になります。

$ python3 hoge3.py 
{'_Hoge3__hoge': 'fuga', '__hoge': 'foo'}

挙動をまとめると

  • Hogeクラス内で定義した __hoge変数は実行前に _Hoge__hogeという名前に置換される。
  • 実行時には hogeクラスのオブジェクトには __hogeという変数は存在しないので参照できない。
  • hogeクラスのオブジェクトが__hoge変数を持たないため、再代入できるように見えた。(実際は新しい属性がオブジェクトに追加された ※ hoge3.pyの実行結果より)

ということになります。
参照はできるのに再代入ができるように見えたのはマングリングが原因でした。

課題

Pythonのこの仕様には2つの問題があるように思われます。

  1. 本来の意味でインスタンス変数を隠蔽できていない(見かけ上は再代入できる)
  2. オブジェクトに自由に属性を追加(おそらく削除も?)できてしまう

これらの課題の解消方法を今後調査・検討していきたいと思います。

1
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2