web-dev-qa-db-ja.com

Pythonのプライベートメソッドとプロテクトメソッドの継承

Pythonには「実際の」プライベート/保護されたメソッドはありません。このアプローチは、何も隠すものではありません。 Pythonが何をするかを理解したいだけです。

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        pass

class Child(Parent):
    def foo(self):
        self._protected()   # This works

    def bar(self):
        self.__private()    # This doesn't work, I get a AttributeError:
                            # 'Child' object has no attribute '_Child__private'

それで、この動作は、「保護された」メソッドは継承されますが、「プライベート」はまったく継承されないということですか?
それとも私は何かを見逃しましたか?

57
uloco

Pythonにはプライバシーモデルがなく、C++、C#、またはJavaのようなアクセス修飾子はありません。本当に「保護された」属性や「プライベート」な属性はありません。

先頭に二重アンダースコアがあり、末尾に二重アンダースコアがない名前は、継承時に衝突から保護するためにmangledです。サブクラスは独自の__private()メソッドを定義でき、これらは親クラスの同じ名前と干渉しません。そのような名前はclass privateと見なされます。それらはクラス外からアクセスできますが、偶然衝突する可能性ははるかに低いです。

マングリングは、そのような名前に追加のアンダースコアとクラス名を追加することによって行われます(名前の使用方法や存在するかどうかに関係なく)、それらに事実上名前空間を与えます。 Parentクラスでは、___private_識別子は(コンパイル時に)名前__Parent__private_に置き換えられますが、Childクラスでは識別子は__Child__private_、クラス定義のどこでも。

以下が機能します:

_class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self._Parent__private()
_

字句解析ドキュメントの Reserved classes of identifiers を参照してください:

___*_
クラスプライベート名。このカテゴリの名前は、クラス定義のコンテキスト内で使用される場合、書き換えられたフォームを使用するように書き換えられ、ベースクラスと派生クラスの「プライベート」属性間の名前の衝突を回避します。

および参照される 名前に関するドキュメント

プライベート名のマングリング:クラス定義にテキストで出現する識別子が2つ以上の下線文字で始まり、2つ以上の下線で終わらない場合、そのクラスのプライベート名と見なされます。プライベート名は、コードが生成される前に長い形式に変換されます。変換により、名前の前にクラス名が挿入され、先頭の下線が削除され、単一の下線が挿入されます。たとえば、Hamという名前のクラスで発生する識別子___spam_は、__Ham__spam_に変換されます。この変換は、識別子が使用される構文コンテキストとは無関係です。

具体的に特定の名前を使用できない、またはクラスを壊す危険性があることをクラスをサブクラス化する開発者に伝える必要がない限り、クラスプライベート名を使用しないでください。公開されたフレームワークとライブラリ以外では、この機能はほとんど使用されません。

PEP 8 Python Style Guide は、プライベート名のマングリングについて次のように述べています:

クラスがサブクラス化されることを意図しており、サブクラスに使用させたくない属性がある場合は、二重アンダースコアを使用し、後続アンダースコアを使用しない名前を付けることを検討してください。これにより、Pythonの名前マングリングアルゴリズムが呼び出され、クラスの名前が属性名に変換されます。これにより、サブクラスに同じ名前の属性が誤って含まれている場合に、属性名の衝突を回避できます。

注1:マングルされた名前では単純なクラス名のみが使用されるため、サブクラスが同じクラス名と属性名の両方を選択した場合でも、名前の衝突が発生する可能性があります。

注2:名前のマングリングは、デバッグや__getattr__()などの特定の用途に使用できますが、あまり便利ではありません。ただし、ネームマングリングアルゴリズムは十分に文書化されており、手動で簡単に実行できます。

注3:すべての人が名前のマングリングを好むわけではありません。偶発的な名前の衝突を避ける必要性と、上級の発信者による潜在的な使用とのバランスをとるようにしてください。

85
Martijn Pieters

ダブル__属性は_ClassName__method_nameに変更され、_method_nameによって暗示されるセマンティックプライバシーよりもプライベートになります。

本当にしたいのであれば、技術的にはまだ取得できますが、おそらく誰もそれをするつもりはないので、コード抽象化の理由から、メソッドはその時点でプライベートである可能性があります。

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        print("Is it really private?")

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self.__private()

c = Child()
c._Parent__private()

これには、メソッドが子クラスのメソッド名と衝突しないようにするという追加の利点(または主な利点と言う人もいます)があります。

11
Tim Wilder

また PEP8

1つの先頭アンダースコアは、non-publicメソッドとインスタンス変数にのみ使用します。

サブクラスとの名前の衝突を回避するには、2つの先頭の下線を使用してPythonの名前マングリングルールを呼び出します。

Pythonはこれらの名前をクラス名と組み合わせます:class Foo__aという名前の属性がある場合、Foo.__aからはアクセスできません。 (しつこいユーザーはFoo._Foo__aを呼び出すことでアクセスできます。)通常、サブクラス化するように設計されたクラスの属性との名前の競合を避けるために、先頭に2つのアンダースコアを使用します。

慣例により、_such_methodsにも近づかないでください。 privateとして扱う必要があるということです

7
Deck

データメンバーをprivateとして宣言することにより:

__private()

クラスの外からはアクセスできません

Pythonは name mangling と呼ばれる手法をサポートしています。

この機能は、2つのアンダースコアが前に付いたクラスメンバーを次のように変換します。

_className.memberName

Child()からアクセスしたい場合は、self._Parent__private()を使用できます。

4
Kobi K

これは古い質問ですが、私はそれに遭遇し、ニースの回避策を見つけました。

保護された関数を模倣したいが、子クラスでは簡単に関数にアクセスしたいため、親クラスでマングルされた名前を付けます。

parent_class_private_func_list = [func for func in dir(Child) if func.startswith ('_Parent__')]

for parent_private_func in parent_class_private_func_list:
        setattr(self, parent_private_func.replace("_Parent__", "_Child"), getattr(self, parent_private_func))        

アイデアは、親の関数名を現在のネームスペースに合わせて手動で置き換えることです。これを子クラスのinit関数に追加した後、簡単な方法で関数を呼び出すことができます。

self.__private()
2
Rohi

知る限り、2番目のケースではPython "name mangling"を実行するため、親クラスの__privateメソッドの名前は実際には次のようになります。

_Parent__private

そして、あなたはこのフォームの子供でもそれを使用することはできません

1
volcano