質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.13%

プライベート変数について教えてほしい

受付中

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 2,354

score 10

プライベート変数は外部からアクセスできないと認識しております。
確かに、ob.num = 321 で値を代入し、ob.print()で確認すると、値は変更できていない ことを確認しましたが、print(ob.num)では値が変更されている、つまりアクセスできいるのですが
どういうことでしょうか。

class MyObj:
    def __init__(self):
        self.__num = 123

    def print(self):
        print(self.__num)

ob = MyObj()
ob.__num = 321
ob.print()
print(ob.__num)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • quickquip

    2019/05/14 14:17 編集

    123
    321
    と意図した結果になりませんか?

    キャンセル

  • test141

    2019/05/14 14:20

    print(ob.__num) を実行すると 123 になると思いました。
    プライベート変数なので、
    ob.__num = 321 を実行してもアクセスできない変更できない認識でいます。

    キャンセル

  • quickquip

    2019/05/14 14:25

    マングリングが起きるのは**いつなのか**という質問だと理解しましたが、すでに回答がついているようなので。

    キャンセル

回答 5

+7

プライベート変数
そういう言葉はありません。少なくともpythonの世界では。

検索 — Python 3.7.3 ドキュメント

ドキュメントにはプライベートな名前と書いてあるみたいですね。

チュートリアルではプライベート変数という言葉も使っているようです。

とりあえず厳密な意味での(絶対に外部から参照できない)「プライベート変数」はpythonにはない、そういう名前で呼ぶことも(原則的には)ない、としておきます。


アンダーバー2つの属性はマングリングによって処理されます。

プライベートな名前のマングリング: クラス定義内に書かれた識別子で、2つ以上のアンダースコアから始まり、末尾が2つ以上のアンダースコアで終わっていないものは、そのクラスの プライベートな名前 とみなされます。プライベートな名前は、コードが生成される前により長い形式に変換されます。この変換によって、クラス名の先頭にアンダースコアがあれば除去し、先頭にアンダースコアを1つ付加し、名前の前に挿入されます。例えば、クラス名 Ham の中の識別子 spam は、_Hamspam に変換されます。変換は識別子が使用されている構文のコンテキストからは独立しています。変換された名前が非常に長い (255文字を超える) 場合、実装によっては名前の切り詰めが行われるかもしれません。クラス名がアンダースコアのみから成る場合は変換は行われません。

6. 式 (expression) — Python 3.7.3 ドキュメント

__num_MyObj__numに変換されるので、外部から参照できない「ように見える」だけです。書き換える場合はこちらに対して代入する必要があります。

ob = MyObj()
ob._MyObj__num = 321
ob.print() # 321

ではob.__numに対して代入すると? というと、前提としてpythonのオブジェクトは代入によって新しい属性を作ることができます。

>>> class A:
...     pass
... 
>>> a = A()
>>> a.hoge
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'hoge'
>>> a.hoge = 42
>>> a.hoge
42

ob.__num = 321という文はクラス定義の外にあるので、マングリングは働かずそのまま__numという属性が新たに作られます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/14 14:45 編集

    > チュートリアルではプライベート変数という言葉も使っているようです。
    記述にあるんだ。。。気持ち悪いなぁ^^;

    キャンセル

+4

Pythonの名前の衝突を避ける仕様が働いています。

print(ob.__num) # -> 321と出力
print(ob._MyObj__num) # -> 123 と出力

9.6. プライベート変数

クラスのプライベートメンバについて適切なユースケース(特にサブクラスで定義された名前との衝突を避ける場合)があるので、マングリング(name mangling) と呼ばれる、限定されたサポート機構があります。 spam (先頭に二個以上の下線文字、末尾に一個以下の下線文字) という形式の識別子は、 _classnamespam へとテキスト置換されるようになりました。ここで classname は、現在のクラス名から先頭の下線文字をはぎとった名前になります。このような難号化 (mangle) は、識別子の文法的な位置にかかわらず行われるので、クラス定義内に現れた識別子全てに対して実行されます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/14 14:26

    あれ、プライベート変数ってチュートリアルで出てくるんですね。なんで検索で引っかからなかったんだろう。

    キャンセル

  • 2019/05/14 14:39

    出てきはするのですが、説明の冒頭に
    >オブジェクトの中からしかアクセス出来ない 「プライベート」 インスタンス変数は、 Python にはありません。
    とあるので、実際は無いよ!と言いながらも、多言語から入ってくる人たちが分かりやすいように「プライベート変数」と銘打っているのだと思います。
    検索は「プライベート」だと引っかかるのですが、「プライベート変数」ではかからないですね…。
    検索インデックスの作られ方が微妙です。

    キャンセル

  • 2019/05/14 14:48

    記述にあるのかと思ったら、微妙な表現なんですね。理解できました。

    キャンセル

+4

一応補足として。

  • マングリングが実施されるのはclass文を実行した時です。
  • マングリングの対象になるのは変数ではありません。クラス定義内にある識別子です。

class Hoge:
    def __init__(__self):
        __hoge = 1
        print(locals())


と書いてHoge()を実行すると

{'_Hoge__hoge': 1, '_Hoge__self': <__main__.Hoge object at 0x10c009390>}


となるのが確認できます。
クラス変数なのかローカル変数なのか引数なのかメソッド名なのかというような、「その識別子がなんであるか?」という区別なしに、マングリング対象になる識別子はすべて書き換えられているわけです。

上のソースは

class Hoge:
    def __init__(_Hoge__self):
        _Hoge__hoge = 1
        print(locals())


とかわりありません。


難号化の規則は主に不慮の事故を防ぐためのものだということに注意してください

とチュートリアルにある通り、この機能は名前をプライベートにするためのものではありません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+3

Pythonのプライベート変数は変数名への変更になっているためです。

class A:
    def __init__(self):
        self.__private = 1
    def print(self):
        print(self.__private)
a = A()
a.__private = 2
a.print()
print(a.__private)

print(vars(a))
1
2
{'_A__private': 1, '__private': 2}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

アンダースコアが二つ付いた変数識別子はマングリングされます。
ただし外部から新たに属性を加える場合はそのままです。

>>> class MyClass:
...     def __init__(self):
...         self.__attr = 1
...
>>> inst = MyClass()
>>> inst.__attr = 2
>>>
>>> dir(inst)
['_MyClass__attr', '__attr', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>>
>>> inst._MyClass__attr
1
>>> inst.__attr
2

別のオブジェクトとして扱われているのが分かります。

コメントを受けて

この両者(self.__attr と inst.__attr)は違うのでしょうか

hayataka2049さんが引用されている文章の中に、次のような記述があります。

クラス定義内に書かれた識別子で ...後略

単にどこに書かれているかが問題なわけです。
次のコードでもそれを確認することができます。

class MyClassA:
    def __init__(self, inst):
        inst.__attr = 42


class MyClassB:
    def __init__(self):
        pass


b = MyClassB()
a = MyClassA(b)

print(vars(b))

実行結果 Wandbox

{'_MyClassA__attr': 42}

MyClassBの属性なのに、MyClassAの名前を冠されているのはなかなか面白い挙動ですね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/14 14:52

    ご回答ありがとうございます。
    クラスを定義しているブロックに下記を追記します。
    >>> class MyClass:
    ... def __init__(self):
    ... self.__attr = 1
    ...
      def print(self):
       print(self.__attr)
    >>> inst = MyClass()
    >>> inst.__attr = 2

    この場合、inst.print()を実行すると、1 が返って来ますよね。
    でも、print(inst.__attr)を実行すると、2 が返ってきます。
    この両者(self.__attr と inst.__attr)は違うのでしょうか

    キャンセル

  • 2019/05/14 17:20

    print(self.__attr)
    も同様に
    print(self._MyClass__attr)
    に変換された結果と捉えるのが一番いいんじゃないでしょうか。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.13%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る