1. ホーム
  2. パイソン

[解決済み】Pythonの「private」メソッドは、なぜ実際にはprivateではないのですか?

2022-03-23 14:31:13

質問

Pythonでは、クラス内に「プライベート」メソッドや変数を作成する機能があります。名前の前にアンダースコアを2つ付けると、次のようになります。 __myPrivateMethod() . では、これをどう説明すればいいのでしょうか。

>>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
...
>>> obj = MyClass()

>>> obj.myPublicMethod()
public method

>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

>>> obj._MyClass__myPrivateMethod()
this is private!!

どうしたんだ!?

よくわからなかった人のために、少し説明します。

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
...
>>> obj = MyClass()

publicメソッドとprivateメソッドを持つクラスを作成し、インスタンス化します。

次に、そのpublicメソッドを呼び出す。

>>> obj.myPublicMethod()
public method

次に、そのプライベートメソッドを呼び出してみる。

>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

ここではすべてがうまくいっているように見えますが、呼び出すことができません。実は「プライベート」なのです。まあ、実際にはそうではないのですが。実行中 dir() は、Pythonがすべての'private'メソッドに対して魔法のように作成する、新しい魔法のメソッドを明らかにします。

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

この新しいメソッドの名前は、常にアンダースコア、クラス名、メソッド名の順になります。

>>> obj._MyClass__myPrivateMethod()
this is private!!

カプセル化はこれくらいにして、え?

いずれにせよ、Pythonはカプセル化をサポートしていないと聞いていたのですが、それならなぜ試してみたのでしょうか?どうなんでしょう?

どうすれば解決するの?

名前のスクランブルは、サブクラスが誤ってスーパークラスのプライベート・メソッドや属性をオーバーライドしないようにするために使用されます。外部からの意図的なアクセスを防ぐためのものではありません。

例えば

>>> class Foo(object):
...     def __init__(self):
...         self.__baz = 42
...     def foo(self):
...         print self.__baz
...     
>>> class Bar(Foo):
...     def __init__(self):
...         super(Bar, self).__init__()
...         self.__baz = 21
...     def bar(self):
...         print self.__baz
...
>>> x = Bar()
>>> x.foo()
42
>>> x.bar()
21
>>> print x.__dict__
{'_Bar__baz': 21, '_Foo__baz': 42}

もちろん、2つの異なるクラスが同じ名前であれば、それは破綻しています。