Vererbung privater und geschützter Methoden in Python

76

Ich weiß, dass es in Python keine "echten" privaten / geschützten Methoden gibt. Dieser Ansatz soll nichts verbergen; Ich möchte nur verstehen, was Python macht.

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'

Bedeutet dieses Verhalten also, dass "geschützte" Methoden vererbt werden, "private" jedoch überhaupt nicht?
Oder habe ich etwas vermisst?

uloco
quelle
Was meinst du mit "das funktioniert nicht"?
tyteen4a03
Ich habe den ursprünglichen Beitrag bearbeitet.
Uloco
6
Sie müssen es so nennen, nehmen wir an, c ist eine Instanz von Childc._Parent__private()
vahid abdi
Funktioniert es nicht so wie es sollte? Private AFAIK-Methoden werden nicht vererbt. stackoverflow.com/questions/8241462/…
Qback

Antworten:

110

Python hat kein Datenschutzmodell, es gibt keine Zugriffsmodifikatoren wie in C ++, C # oder Java. Es gibt keine wirklich "geschützten" oder "privaten" Attribute.

Namen mit einem führenden doppelten Unterstrich und keinem nachfolgenden doppelten Unterstrich werden entstellt , um sie bei der Vererbung vor Zusammenstößen zu schützen. Unterklassen können ihre eigene __private()Methode definieren , und diese stören nicht den gleichen Namen in der übergeordneten Klasse. Solche Namen gelten als klassenprivat ; Sie sind immer noch von außerhalb der Klasse zugänglich, aber es ist weitaus weniger wahrscheinlich, dass sie versehentlich zusammenstoßen.

Mangling wird durchgeführt, indem einem solchen Namen ein zusätzlicher Unterstrich und der Klassenname vorangestellt werden (unabhängig davon, wie der Name verwendet wird oder ob er existiert), wodurch ihnen effektiv ein Namespace zugewiesen wird . In der ParentKlasse wird jeder __privateBezeichner (zur Kompilierungszeit) durch den Namen ersetzt _Parent__private, während in der ChildKlasse der Bezeichner _Child__privateüberall in der Klassendefinition durch ersetzt wird .

Folgendes wird funktionieren:

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

    def bar(self):
        self._Parent__private()

Siehe Reservierte Klassen von Bezeichnern in der Dokumentation zur lexikalischen Analyse:

__*
Klassenprivate Namen. Namen in dieser Kategorie werden, wenn sie im Kontext einer Klassendefinition verwendet werden, neu geschrieben, um ein verstümmeltes Formular zu verwenden, um Namenskonflikte zwischen „privaten“ Attributen von Basisklassen und abgeleiteten Klassen zu vermeiden.

und die referenzierte Dokumentation zu Namen :

Mangeln von privaten Namen : Wenn ein Bezeichner, der in einer Klassendefinition in Textform vorkommt, mit zwei oder mehr Unterstrichen beginnt und nicht mit zwei oder mehr Unterstrichen endet, wird er als privater Name dieser Klasse betrachtet. Private Namen werden in eine längere Form umgewandelt, bevor Code für sie generiert wird. Bei der Umwandlung wird der Klassenname eingefügt, wobei führende Unterstriche entfernt und ein einzelner Unterstrich vor dem Namen eingefügt wird. Beispielsweise wird der __spamin einer Klasse mit dem Namen Ham vorkommende Bezeichner in transformiert _Ham__spam. Diese Transformation ist unabhängig vom syntaktischen Kontext, in dem der Bezeichner verwendet wird.

Verwenden Sie keine klassenprivaten Namen, es sei denn, Sie möchten Entwicklern, die Ihre Klasse unterordnen möchten, ausdrücklich mitteilen, dass sie bestimmte Namen nicht verwenden können oder das Risiko eingehen, Ihre Klasse zu beschädigen. Außerhalb veröffentlichter Frameworks und Bibliotheken wird diese Funktion kaum verwendet.

Der PEP 8 Python Style Guide enthält Folgendes zum Mangeln von privaten Namen:

Wenn Ihre Klasse als Unterklasse gedacht ist und Sie Attribute haben, die keine Unterklassen verwenden sollen, sollten Sie sie mit doppelt führenden Unterstrichen und keinen nachfolgenden Unterstrichen benennen. Dies ruft den Namensveränderungsalgorithmus von Python auf, bei dem der Name der Klasse in den Attributnamen entstellt wird. Dies hilft, Kollisionen von Attributnamen zu vermeiden, falls Unterklassen versehentlich Attribute mit demselben Namen enthalten.

Hinweis 1: Beachten Sie, dass im verstümmelten Namen nur der einfache Klassenname verwendet wird. Wenn also eine Unterklasse denselben Klassennamen und denselben Attributnamen auswählt, können Sie dennoch Namenskollisionen erhalten.

Hinweis 2: Das Mangeln von Namen kann bestimmte Verwendungszwecke haben, z. B. das Debuggen und __getattr__()ist weniger bequem. Der Name Mangling-Algorithmus ist jedoch gut dokumentiert und einfach manuell durchzuführen.

Anmerkung 3: Nicht jeder mag das Mangeln von Namen. Versuchen Sie, die Notwendigkeit, versehentliche Namenskonflikte zu vermeiden, mit der potenziellen Verwendung durch fortgeschrittene Anrufer in Einklang zu bringen.

Martijn Pieters
quelle
Ich wusste, es hatte etwas mit diesem Namen zu tun. Ich wusste, dass ich über _Parent__private von außerhalb der Klasse auf die Methode zugreifen konnte, aber ich habe einfach nicht verstanden, warum ich sie nicht innerhalb der geerbten Klasse aufrufen konnte. Ich wusste nicht, dass Unterklassen ihre eigenen bekommen __private(), vielen Dank für diese klare Antwort!
Uloco
Was ist mit __getstate__()und __setstate__()? Ein Kind könnte diese Methoden nicht von seinem Elternteil erben?
BoltzmannBrain
@BoltzmannBrain: Diese Namen enthalten auch am Ende Unterstriche, nicht nur am Anfang des Namens. Das ist eine andere Klasse von Namen . Siehe die Dokumentation, mit der ich verlinkt habe .
Martijn Pieters
Danke, aber die Dokumente dort geben mir nicht viel mehr nützliche Informationen. Ich habe dies auf eine andere Frage verschoben
BoltzmannBrain
@ Martinijn: Danke für die Antwort. Vielleicht verwende ich a nicht, pythonic stylewenn ich versuche, alle Variablen innerhalb einer Klasse privat zu machen und dann gettersund settersfür diese privaten Variablen zu verwenden. Nun, ich sollte diese Frage zur Verwendung privater Variablen googeln.
FooBar167
12

Das __Doppelattribut wird geändert, _ClassName__method_namewodurch es privater wird als die von implizierte semantische Privatsphäre _method_name.

Sie können technisch immer noch darauf zugreifen, wenn Sie es wirklich möchten, aber vermutlich wird dies niemand tun. Aus Gründen der Codeabstraktion kann die Methode zu diesem Zeitpunkt genauso gut privat sein.

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()

Dies hat den zusätzlichen Vorteil (oder einige würden sagen, der primäre Vorteil), dass eine Methode nicht mit Methodennamen von untergeordneten Klassen kollidieren darf.

Tim Wilder
quelle
Dies funktioniert nicht bei einem Anruf innerhalb des Kindes mit der privaten Methode "Parent"
Nico Coallier,
8

Auch PEP8 sagt

Verwenden Sie einen führenden Unterstrich nur für nicht öffentliche Methoden und Instanzvariablen.

Um Namenskonflikte mit Unterklassen zu vermeiden, verwenden Sie zwei führende Unterstriche, um Pythons Regeln für die Namensverfälschung aufzurufen.

Python entstellt diese Namen mit dem Klassennamen: Wenn class Fooein Attribut benannt ist __a, kann nicht auf dieses zugegriffen werden Foo.__a. (Ein beharrlicher Benutzer kann weiterhin durch Aufrufen Zugriff erhalten Foo._Foo__a.) Im Allgemeinen sollten doppelt führende Unterstriche nur verwendet werden, um Namenskonflikte mit Attributen in Klassen zu vermeiden, die für Unterklassen ausgelegt sind.

Sie sollten sich _such_methodsauch konventionell fernhalten . Ich meine, du solltest sie so behandelnprivate

Deck
quelle
7

Indem Sie Ihr Datenmitglied als privat deklarieren:

__private()

Sie können einfach nicht von außerhalb der Klasse darauf zugreifen

Python unterstützt eine Technik namens Name Mangling .

Diese Funktion verwandelt Klassenmitglieder mit zwei Unterstrichen in:

_className.memberName

Wenn Sie von dort aus darauf zugreifen möchten, können Child()Sie Folgendes verwenden:self._Parent__private()

Kobi K.
quelle
3

Obwohl dies eine alte Frage ist, bin ich darauf gestoßen und habe eine gute Lösung gefunden.

In dem Fall, in dem Sie den Namen der übergeordneten Klasse entstellt haben, weil Sie eine geschützte Funktion imitieren wollten, aber dennoch auf einfache Weise auf die Funktion der untergeordneten Klasse zugreifen möchten.

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))        

Die Idee ist, den übergeordneten Funktionsnamen manuell durch eine Anpassung an den aktuellen Namespace zu ersetzen. Nachdem Sie dies in die init-Funktion der untergeordneten Klasse eingefügt haben, können Sie die Funktion auf einfache Weise aufrufen.

self.__private()
Rohi
quelle
2

AFAIK, im zweiten Fall führt Python "Name Mangling" durch, daher lautet der Name der __private-Methode der übergeordneten Klasse wirklich:

_Parent__private

Und Sie können es auch nicht in dieser Form für Kinder verwenden

Vulkan
quelle