Warum sind Pythons "private" Methoden nicht wirklich privat?

657

Python gibt uns die Möglichkeit, 'private' Methoden und Variablen innerhalb einer Klasse zu erstellen, indem dem Namen doppelte Unterstriche vorangestellt werden : __myPrivateMethod(). Wie kann man das dann erklären?

>>> 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 "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
>>> obj._MyClass__myPrivateMethod()
this is private!!

Was ist das Problem?!

Ich werde dies ein wenig für diejenigen erklären, die das nicht ganz verstanden haben.

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

Dort habe ich eine Klasse mit einer öffentlichen und einer privaten Methode erstellt und instanziiert.

Als nächstes rufe ich seine öffentliche Methode auf.

>>> obj.myPublicMethod()
public method

Als nächstes versuche ich, seine private Methode aufzurufen.

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

Hier sieht alles gut aus; wir können es nicht nennen. Es ist in der Tat "privat". Nun, eigentlich ist es nicht. Wenn Sie dir () für das Objekt ausführen, wird eine neue magische Methode angezeigt, die Python auf magische Weise für alle Ihre "privaten" Methoden erstellt.

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

Der Name dieser neuen Methode ist immer ein Unterstrich, gefolgt vom Klassennamen, gefolgt vom Methodennamen.

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

Soviel zur Kapselung, was?

Auf jeden Fall hatte ich immer gehört, dass Python die Kapselung nicht unterstützt. Warum also überhaupt versuchen? Was gibt?

Willurd
quelle
18
Gleiches gilt für Java oder C #, wenn Sie Reflection verwenden (was Sie dort irgendwie tun).
0x434D53
4
Es wurde für Unit-Test-Zwecke erstellt, sodass Sie diesen "Hack" verwenden können, um die privaten Methoden Ihrer Klasse von außen zu testen.
waas1919
16
Ist das Testen privater Methoden nicht ein Anti-Pattern? Private Methoden werden in einigen öffentlichen Methoden verwendet, da sie sonst für immer nicht verwendet werden. Und der richtige Weg, private Methoden zu testen (basierend auf meinen bisherigen Erfahrungen mit ThoughtWorks), besteht darin, dass Sie nur Tests für öffentliche Methoden schreiben, die alle Fälle abdecken. Wenn das gut funktioniert, müssen Sie private Methoden überhaupt nicht von außen testen.
Vishnu Narang
3
@ VishnuNarang: Ja, das wird oft gelehrt. Aber wie immer ist ein fast "religiöser" Ansatz von " immer das tun, niemals das tun" das einzige, was "niemals" gut ist. Wenn Unit-Tests "nur" für Regressionstests oder zum Testen der öffentlichen API verwendet werden, müssen Sie keine Privaten testen. Wenn Sie jedoch eine Unit-Test-gesteuerte Entwicklung durchführen, gibt es gute Gründe, private Methoden während der Entwicklung zu testen (z. B. wenn es schwierig ist, bestimmte ungewöhnliche / extreme Parameter über die öffentliche Schnittstelle zu verspotten). In einigen Sprachen / Unit-Test-Umgebungen ist dies nicht möglich, was meiner Meinung nach nicht gut ist.
Marco Freudenberger
5
@MarcoFreudenberger Ich verstehe Ihren Standpunkt. Ich habe Erfahrung in der Entwicklung von Unit-Tests. Wenn es schwierig wird, Parameter zu verspotten, wird dies häufig durch Ändern und Verbessern des Designs behoben. Ich bin noch nicht auf ein Szenario gestoßen, in dem das Design perfekt ist und es dennoch äußerst schwierig ist, Unit-Tests zu vermeiden, um private Methoden zu testen. Ich werde nach solchen Fällen Ausschau halten. Vielen Dank. Ich würde mich freuen, wenn Sie mir vielleicht ein Szenario aus dem Kopf teilen könnten, um mir das Verständnis zu erleichtern.
Vishnu Narang

Antworten:

592

Die Namensverschlüsselung wird verwendet, um sicherzustellen, dass Unterklassen die privaten Methoden und Attribute ihrer Oberklassen nicht versehentlich überschreiben. Es soll nicht den absichtlichen Zugriff von außen verhindern.

Zum Beispiel:

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

Natürlich bricht es zusammen, wenn zwei verschiedene Klassen den gleichen Namen haben.

Alya
quelle
12
docs.python.org/2/tutorial/classes.html . Abschnitt: 9.6 zu privaten Variablen und klassenlokalen Referenzen.
Gjain
72
Für diejenigen von uns, die zu faul sind, um zu scrollen / zu suchen: Abschnitt 9.6 direkter Link
cod3monk3y
3
Sie sollten einen einzelnen Unterstrich einfügen, um anzugeben, dass die Variable als privat betrachtet werden soll. Auch dies hindert niemanden daran, tatsächlich darauf zuzugreifen.
igon
14
Guido beantwortete diese Frage - "Hauptgrund dafür, dass (fast) alles auffindbar gemacht wurde, war das Debuggen: Beim Debuggen muss man oft die Abstraktionen durchbrechen" - ich fügte es als Kommentar hinzu, weil es zu spät ist - zu viele Antworten.
Peter M. - steht für Monica
1
Wenn Sie das Kriterium "Verhindern des absichtlichen Zugriffs" verwenden, unterstützen die meisten OOP-Sprachen keine wirklich privaten Mitglieder. In C ++ haben Sie beispielsweise unformatierten Zugriff auf den Speicher, und in C # kann vertrauenswürdiger Code die private Reflektion verwenden.
CodesInChaos
207

Beispiel einer privaten Funktion

import re
import inspect

class MyClass :

    def __init__(self) :
        pass

    def private_function ( self ) :
        try :
            function_call = inspect.stack()[1][4][0].strip()

            # See if the function_call has "self." in the begining
            matched = re.match( '^self\.', function_call )
            if not matched :
                print 'This is Private Function, Go Away'
                return
        except :
            print 'This is Private Function, Go Away'
            return

        # This is the real Function, only accessible inside class #
        print 'Hey, Welcome in to function'

    def public_function ( self ) :
        # i can call private function from inside the class
        self.private_function()

### End ###
arun
quelle
12
self = MyClass() self.private_function(). : D Natürlich funktioniert es nicht in Klassen, aber Sie müssen nur eine benutzerdefinierte Funktion definieren: def foo(self): self.private_function()
Casey Kuball
161
Nur für den Fall, dass es nicht klar war: Mach das niemals in echtem Code;)
Sudo Bash
10
@ThorSummoner Oder einfach function_call.startswith('self.').
Nyuszika7h
13
inspect.stack()[1][4][0].strip()<- Was sind diese magischen Zahlen 1, 4 und 0?
Akhy
5
Dies kann ganz einfach dadurch beseitigt werden self = MyClass(); self.private_function()und schlägt fehl, wenn es x = self.private_function()innerhalb einer Methode aufgerufen wird.
Will
171

Als ich zum ersten Mal von Java nach Python kam, hasste ich das. Es hat mich zu Tode erschreckt.

Heute ist es vielleicht das Einzige, was ich an Python am meisten liebe .

Ich liebe es, auf einer Plattform zu sein, auf der sich die Leute gegenseitig vertrauen und nicht das Gefühl haben, undurchdringliche Wände um ihren Code bauen zu müssen. Wenn in stark gekapselten Sprachen eine API einen Fehler aufweist und Sie herausgefunden haben, was schief geht, können Sie ihn möglicherweise immer noch nicht umgehen, da die erforderliche Methode privat ist. In Python lautet die Einstellung: "sicher". Wenn Sie denken, Sie verstehen die Situation, vielleicht haben Sie sie sogar gelesen, dann können wir nur "viel Glück!" Sagen.

Denken Sie daran, dass die Kapselung nicht einmal schwach mit "Sicherheit" zusammenhängt oder die Kinder vom Rasen fernhält. Es ist nur ein weiteres Muster, das verwendet werden sollte, um das Verständnis einer Codebasis zu erleichtern.

Thomas Ahle
quelle
36
@ CamJackson Javascript ist dein Beispiel? Die einzige weit verbreitete Sprache mit prototybe-basierter Vererbung und einer Sprache, die funktionale Programmierung bevorzugt? Ich denke, JS ist viel schwieriger zu lernen als die meisten anderen Sprachen, da es mehrere orthogonale Schritte von der traditionellen OOP erfordert. Nicht dass dies Idioten daran hindert, JS zu schreiben, sie wissen es einfach nicht;)
K.Steff
23
APIs sind tatsächlich ein gutes Beispiel dafür, warum Kapselung wichtig ist und wann private Methoden bevorzugt werden. Eine Methode, die privat sein soll, kann bei jeder nachfolgenden neuen Version verschwinden, die Signatur ändern oder das schlimmste aller Änderungsverhalten - alles ohne Vorwarnung. Wird sich Ihr intelligentes, erwachsenes Teammitglied wirklich daran erinnern, dass es in einem Jahr, wenn Sie ein Update durchführen, auf eine Methode zugegriffen hat, die für den privaten Gebrauch bestimmt ist? Wird sie dort überhaupt noch arbeiten?
Unschuldig
2
Ich bin mit dem Argument nicht einverstanden. Im Produktionscode würde ich höchstwahrscheinlich niemals eine API verwenden, die einen Fehler aufweist, durch den ich öffentliche Mitglieder ändere, damit sie "funktioniert". Eine API sollte funktionieren. Wenn dies nicht der Fall ist, würde ich einen Fehlerbericht einreichen oder dieselbe API selbst erstellen. Ich mag die Philosophie nicht und ich mag Python nicht sehr, obwohl seine Syntax es Spaß macht, kleinere Skripte in ... zu schreiben
Yngve Sneen Lindal
4
Java hat Method.setAccessible und Field.setAccessible. Auch beängstigend?
Tony
15
Die Durchsetzung in Java und C ++ erfolgt nicht, weil Java dem Benutzer misstraut, während Python dies tut. Dies liegt daran, dass der Compiler und / oder VM verschiedene Annahmen treffen können, wenn er diese Informationen kennt, wenn er diese Informationen kennt. Beispielsweise kann C ++ eine ganze Indirektionsebene überspringen, indem reguläre alte C-Aufrufe anstelle von virtuellen Aufrufen verwendet werden ist wichtig, wenn Sie an hochleistungsfähigen oder hochpräzisen Dingen arbeiten. Python kann die Informationen von Natur aus nicht wirklich gut nutzen, ohne ihre Dynamik zu beeinträchtigen. Beide Sprachen zielen auf unterschiedliche Dinge ab, also sind beide nicht "falsch"
Shayne
144

Von http://www.faqs.org/docs/diveintopython/fileinfo_private.html

Streng genommen sind private Methoden außerhalb ihrer Klasse zugänglich, nur nicht leicht zugänglich. Nichts in Python ist wirklich privat. Intern werden die Namen privater Methoden und Attribute im laufenden Betrieb entstellt und entwirrt, damit sie unter ihren Vornamen nicht zugänglich sind. Sie können auf die __parse-Methode der MP3FileInfo-Klasse unter dem Namen _MP3FileInfo__parse zugreifen. Erkennen Sie an, dass dies interessant ist, und versprechen Sie dann, es niemals in echtem Code zu tun. Private Methoden sind aus einem bestimmten Grund privat, aber wie viele andere Dinge in Python ist ihre Privatsphäre letztendlich eine Frage der Konvention, nicht der Gewalt.

xsl
quelle
201
oder wie Guido van Rossum es ausdrückte: "Wir sind alle Erwachsene."
35
-1: das ist einfach falsch. Doppelte Unterstriche dürfen niemals als private Unterstriche verwendet werden. Die Antwort von Alya unten zeigt die wahre Absicht der Syntax der Namensverfälschung. Die eigentliche Konvention ist ein einziger Unterstrich.
Nosklo
2
Versuchen Sie es mit nur einem Unterstrich und Sie werden das Ergebnis sehen, das Sie erhalten. @nosklo
Billal Begueradj
93

Der häufig verwendete Ausdruck lautet "Wir stimmen hier allen Erwachsenen zu". Indem Sie einen einzelnen Unterstrich (nicht verfügbar machen) oder einen doppelten Unterstrich (ausblenden) voranstellen, teilen Sie dem Benutzer Ihrer Klasse mit, dass das Mitglied in irgendeiner Weise "privat" sein soll. Sie vertrauen jedoch darauf, dass sich alle anderen verantwortungsbewusst verhalten und dies respektieren, es sei denn, sie haben einen zwingenden Grund, dies nicht zu tun (z. B. Debugger, Code-Vervollständigung).

Wenn Sie wirklich etwas Privates haben müssen, können Sie es in einer Erweiterung implementieren (z. B. in C für CPython). In den meisten Fällen lernen Sie jedoch einfach die pythonische Vorgehensweise.

Tony Meyer
quelle
Gibt es also eine Art Wrapper-Protokoll, mit dem ich auf eine geschützte Variable zugreifen soll?
Intuitiert
3
Es gibt nicht mehr "geschützte" Variablen als "private". Wenn Sie auf ein Attribut zugreifen möchten, das mit einem Unterstrich beginnt, können Sie dies einfach tun (beachten Sie jedoch, dass der Autor davon abrät). Wenn Sie auf ein Attribut zugreifen müssen, das mit einem doppelten Unterstrich beginnt, können Sie den Namen selbst entstellen, möchten dies aber mit ziemlicher Sicherheit nicht.
Tony Meyer
33

Es ist nicht so, dass Sie die Privatsphäre von Mitgliedern in keiner Sprache umgehen können (Zeigerarithmetik in C ++, Reflections in .NET / Java).

Der Punkt ist, dass Sie eine Fehlermeldung erhalten, wenn Sie versuchen, die private Methode versehentlich aufzurufen. Aber wenn Sie sich in den Fuß schießen wollen, machen Sie es einfach.

Bearbeiten: Sie versuchen nicht, Ihre Sachen durch OO-Kapselung zu sichern, oder?

Maximilian
quelle
2
Ganz und gar nicht. Ich mache nur den Punkt, dass es seltsam ist, dem Entwickler eine einfache und meiner Meinung nach magische Möglichkeit zu geben, auf "private" Immobilien zuzugreifen.
Willurd
2
Ja, ich habe nur versucht, den Punkt zu veranschaulichen. Wenn Sie es privat machen, heißt es nur "Sie sollten nicht direkt darauf zugreifen", indem Sie den Compiler beschweren. Aber man will es wirklich wirklich tun, was er kann. Aber ja, in Python ist es einfacher als in den meisten anderen Sprachen.
Maximilian
7
In Java können Sie Inhalte tatsächlich durch Kapselung sichern. Dazu müssen Sie jedoch intelligent sein, den nicht vertrauenswürdigen Code in einem SecurityManager ausführen und sehr vorsichtig sein. Sogar Oracle versteht es manchmal falsch.
Antimon
12

Die class.__stuffNamenskonvention teilt dem Programmierer mit, dass er nicht __stuffvon außen darauf zugreifen soll . Der Name Mangling macht es unwahrscheinlich, dass jemand es versehentlich tun wird.

Sie können das zwar noch umgehen, es ist sogar noch einfacher als in anderen Sprachen (die Sie übrigens auch zulassen), aber kein Python-Programmierer würde dies tun, wenn er sich um die Kapselung kümmert.

Nickolay
quelle
12

Ein ähnliches Verhalten tritt auf, wenn Modulattributnamen mit einem einzelnen Unterstrich beginnen (z. B. _foo).

Als solche bezeichnete Modulattribute werden bei Verwendung der from*Methode nicht in ein importierendes Modul kopiert , z.

from bar import *

Dies ist jedoch eine Konvention und keine Sprachbeschränkung. Dies sind keine privaten Attribute. Sie können von jedem Importeur referenziert und manipuliert werden. Einige argumentieren, dass Python aus diesem Grund keine echte Kapselung implementieren kann.

Ross
quelle
12

Dies ist nur eine dieser Optionen für das Sprachdesign. In gewisser Hinsicht sind sie gerechtfertigt. Sie machen es so, dass Sie ziemlich weit aus dem Weg gehen müssen, um zu versuchen, die Methode aufzurufen, und wenn Sie es wirklich so dringend brauchen, müssen Sie einen ziemlich guten Grund haben!

Debugging-Hooks und Tests kommen als mögliche Anwendungen in den Sinn, die natürlich verantwortungsbewusst eingesetzt werden.

ctcherry
quelle
4

Mit Python 3.4 ist dies das Verhalten:

>>> class Foo:
        def __init__(self):
                pass
        def __privateMethod(self):
                return 3
        def invoke(self):
                return self.__privateMethod()


>>> help(Foo)
Help on class Foo in module __main__:

class Foo(builtins.object)
 |  Methods defined here:
 |
 |  __init__(self)
 |
 |  invoke(self)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

 >>> f = Foo()
 >>> f.invoke()
 3
 >>> f.__privateMethod()
 Traceback (most recent call last):
   File "<pyshell#47>", line 1, in <module>
     f.__privateMethod()
 AttributeError: 'Foo' object has no attribute '__privateMethod'

https://docs.python.org/3/tutorial/classes.html#tut-private

Beachten Sie, dass die Mangling-Regeln hauptsächlich dazu dienen, Unfälle zu vermeiden. Es ist weiterhin möglich, auf eine Variable zuzugreifen oder diese zu ändern, die als privat betrachtet wird. Dies kann sogar unter besonderen Umständen nützlich sein, beispielsweise im Debugger.

Selbst wenn die Frage alt ist, hoffe ich, dass mein Ausschnitt hilfreich sein könnte.

Alberto
quelle
2

Das wichtigste Anliegen bei privaten Methoden und Attributen ist, den Entwicklern anzuweisen, sie nicht außerhalb der Klasse aufzurufen. Dies ist die Kapselung. Man kann die Sicherheit durch Kapselung falsch verstehen. Wenn man absichtlich die von Ihnen erwähnte Syntax (unten) verwendet, möchten Sie keine Kapselung.

obj._MyClass__myPrivateMethod()

Ich bin von C # migriert und anfangs war es auch komisch für mich, aber nach einer Weile kam ich auf die Idee, dass nur die Art und Weise, wie Python-Code-Designer über OOP denken, anders ist.

Afshin Amiri
quelle
1

Warum sind Pythons "private" Methoden nicht wirklich privat?

So wie ich es verstehe, können sie nicht privat sein. Wie könnte die Privatsphäre durchgesetzt werden?

Die offensichtliche Antwort lautet "Auf private Mitglieder kann nur über zugegriffen werden self", aber das würde nicht funktionieren - selfist in Python nichts Besonderes, es ist nichts weiter als ein häufig verwendeter Name für den ersten Parameter einer Funktion.

user200783
quelle