Pourquoi les méthodes «privées» de Python ne sont-elles pas réellement privées?

658

Python nous donne la possibilité de créer des méthodes et des variables « privées » au sein d' une classe par préfixer doubles underscores au nom, comme celui - ci: __myPrivateMethod(). Comment alors expliquer cela

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

Quel est le problème?!

Je vais l'expliquer un peu à ceux qui ne l'ont pas bien compris.

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

Ce que j'ai fait là-bas, c'est créer une classe avec une méthode publique et une méthode privée et l'instancier.

Ensuite, j'appelle sa méthode publique.

>>> obj.myPublicMethod()
public method

Ensuite, j'essaie d'appeler sa méthode privée.

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

Tout semble bien ici; nous ne pouvons pas l'appeler. C'est, en fait, «privé». Eh bien, ce n'est pas le cas. L'exécution de dir () sur l'objet révèle une nouvelle méthode magique que python crée par magie pour toutes vos méthodes «privées».

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

Le nom de cette nouvelle méthode est toujours un trait de soulignement, suivi du nom de la classe, suivi du nom de la méthode.

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

Voilà pour l'encapsulation, hein?

Dans tous les cas, j'avais toujours entendu dire que Python ne supportait pas l'encapsulation, alors pourquoi même essayer? Ce qui donne?

Willurd
la source
18
Il en va de même pour Java ou C # si vous utilisez la réflexion (ce qui est en quelque sorte ce que vous faites là-bas).
0x434D53
4
Il a été construit à des fins de test unitaire, vous pouvez donc utiliser ce "hack" afin de tester de manière externe les méthodes privées de votre classe.
waas1919
16
Le test des méthodes privées n'est-il pas un anti-modèle? Les méthodes privées seront utilisées dans certaines méthodes publiques, bien sûr, elles ne sont pas utilisées pour toujours. Et la bonne façon de tester des méthodes privées (basée sur mon apprentissage jusqu'ici de ThoughtWorks) est que vous écrivez des tests pour les méthodes publiques uniquement qui couvrent tous les cas. Si cela fonctionne bien, vous n'avez pas du tout besoin de tester des méthodes privées de l'extérieur.
Vishnu Narang
3
@VishnuNarang: Oui, c'est ce qui est souvent enseigné. Mais comme toujours, une approche presque "religieuse" consistant à " toujours faire ceci, ne jamais faire cela" est la seule chose qui "jamais" soit bonne. Si les tests unitaires sont "uniquement" utilisés pour les tests de régression ou les tests d'API publique, vous n'avez pas besoin de tester les données privées. Mais si vous faites du développement piloté par les tests unitaires, il y a de bonnes raisons de tester les méthodes privées pendant le développement (par exemple, quand il est difficile de se moquer de certains paramètres inhabituels / extrêmes via l'interface publique). Certains langages / environnements de test unitaire ne vous permettent pas de le faire, ce qui n'est pas bon à mon humble avis.
Marco Freudenberger
5
@MarcoFreudenberger Je vois votre point. J'ai de l'expérience en développement piloté par les tests unitaires. Souvent, lorsqu'il devient difficile de se moquer des paramètres, le plus souvent, il est résolu en modifiant et en améliorant la conception. Je n'ai pas encore rencontré de scénario où la conception est parfaite et où les tests unitaires sont extrêmement difficiles à éviter pour tester des méthodes privées. Je vais chercher de tels cas. Merci. J'apprécierais si vous pouviez partager un scénario du haut de votre tête pour m'aider à comprendre.
Vishnu Narang

Réponses:

592

Le brouillage de nom est utilisé pour garantir que les sous-classes ne remplacent pas accidentellement les méthodes privées et les attributs de leurs superclasses. Il n'est pas conçu pour empêcher l'accès délibéré de l'extérieur.

Par exemple:

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

Bien sûr, il tombe en panne si deux classes différentes ont le même nom.

Alya
la source
12
docs.python.org/2/tutorial/classes.html . Section: 9.6 sur les variables privées et les références locales de classe.
gjain
72
Pour ceux d'entre nous trop paresseux pour faire défiler / rechercher: Section 9.6 lien direct
cod3monk3y
3
Vous devez mettre un seul trait de soulignement pour spécifier que la variable doit être considérée comme privée. Encore une fois, cela n'empêche pas quelqu'un d'accéder réellement à cela.
igon
14
Guido a répondu à cette question - "la raison principale pour rendre (presque) tout ce qui était découvrable était le débogage: lorsque vous déboguez, vous devez souvent briser les abstractions" - je l'ai ajouté comme commentaire car il est trop tard - trop de réponses.
Peter M. - signifie Monica
1
Si vous respectez le critère "empêcher l'accès délibéré", la plupart des langues de POO ne prennent pas en charge les membres véritablement privés. Par exemple, en C ++, vous avez un accès brut à la mémoire et en code de confiance C #, vous pouvez utiliser la réflexion privée.
CodesInChaos
207

Exemple de fonction privée

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
la source
12
self = MyClass() self.private_function(). : D Bien sûr, cela ne fonctionne pas en classe, mais il suffit de définir une fonction personnalisée: def foo(self): self.private_function()
Casey Kuball
161
Juste au cas où ce ne serait pas clair: ne faites jamais cela en vrai code;)
Sudo Bash
10
@ThorSummoner Ou tout simplement function_call.startswith('self.').
nyuszika7h
13
inspect.stack()[1][4][0].strip()<- quels sont ces nombres magiques 1, 4 et 0?
akhy
5
Cela peut être vaincu assez facilement en faisant self = MyClass(); self.private_function()et il échoue lorsqu'il est appelé à l'aide d' x = self.private_function()une méthode.
Will
171

Quand je suis arrivé de Java à Python, je détestais ça. Cela m'a fait peur à mort.

Aujourd'hui, c'est peut-être la seule chose que j'aime le plus à propos de Python.

J'adore être sur une plate-forme, où les gens se font confiance et ne se sentent pas obligés de construire des murs impénétrables autour de leur code. Dans les langues fortement encapsulées, si une API a un bogue et que vous avez compris ce qui ne va pas, vous ne pourrez toujours pas le contourner car la méthode requise est privée. En Python, l'attitude est: "sûr". Si vous pensez comprendre la situation, vous l'avez peut-être même lue, alors tout ce que nous pouvons dire, c'est "bonne chance!".

N'oubliez pas que l'encapsulation n'est même pas faiblement liée à la «sécurité» ou au fait de garder les enfants hors de la pelouse. C'est juste un autre modèle qui devrait être utilisé pour rendre une base de code plus facile à comprendre.

Thomas Ahle
la source
36
@CamJackson Javascript est votre exemple ?? Le seul langage largement utilisé qui a l'héritage basé sur prototybe et un langage qui favorise la programmation fonctionnelle? Je pense que JS est beaucoup plus difficile à apprendre que la plupart des autres langues, car il prend plusieurs étapes orthogonales de la POO traditionnelle. Non pas que cela empêche les idiots d'écrire JS, ils ne le savent tout simplement pas;)
K.Steff
23
Les API sont en fait un très bon exemple de l'importance de l'encapsulation et du moment où les méthodes privées seraient préférées. Une méthode censée être privée peut disparaître, modifier la signature ou, pire encore, modifier le comportement - le tout sans avertissement - sur toute nouvelle version ultérieure. Votre membre d'équipe intelligent et adulte se souviendra-t-il vraiment qu'elle a accédé à une méthode destinée à être privée dans un an à compter de la mise à jour? Va-t-elle même y travailler encore?
einnocent
2
Je suis en désaccord avec l'argument. Dans le code de production, je n'utiliserais probablement jamais une API qui a un bug qui me fait changer de membre public pour le faire "fonctionner". Une API devrait fonctionner. Si ce n'est pas le cas, je déposerais un rapport de bogue ou ferais moi-même la même API. Je n'aime pas la philosophie et je n'aime pas beaucoup Python, bien que sa syntaxe le rend amusant à écrire des scripts plus petits en ...
Yngve Sneen Lindal
4
Java a Method.setAccessible et Field.setAccessible. Aussi effrayant?
Tony
15
L'application en Java et C ++ n'est pas parce que Java se méfie de l'utilisateur alors que Python le fait. C'est parce que le compilateur et / ou vm peuvent faire diverses hypothèses lorsqu'il traite de la façon dont il gère ses affaires s'il connaît ces informations, par exemple C ++ peut ignorer une couche entière d'indirection en utilisant de vieux appels C normaux au lieu d'appels virtuels, et que compte lorsque vous travaillez sur des trucs de haute performance ou de haute précision. Python, de par sa nature, ne peut pas vraiment faire bon usage de l'information sans compromettre son dynamisme. Les deux langues visent des choses différentes, donc aucune n'est "fausse"
Shayne
144

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

À strictement parler, les méthodes privées sont accessibles en dehors de leur classe, mais pas facilement accessibles. Rien en Python n'est vraiment privé; en interne, les noms des méthodes et attributs privés sont mutilés et démêlés à la volée pour les rendre inaccessibles par leurs prénoms. Vous pouvez accéder à la méthode __parse de la classe MP3FileInfo sous le nom _MP3FileInfo__parse. Reconnaissez que c'est intéressant, puis promettez de ne jamais le faire en vrai code. Les méthodes privées sont privées pour une raison, mais comme beaucoup d'autres choses en Python, leur caractère privé est finalement une question de convention, pas de force.

xsl
la source
202
ou comme l'a dit Guido van Rossum: "nous sommes tous des adultes".
35
-1: c'est tout simplement faux. Les doubles soulignements ne sont jamais destinés à être utilisés comme privés en premier lieu. La réponse d'Alya ci-dessous indique l'intention réelle de la syntaxe de la manipulation de noms. La vraie convention est un simple trait de soulignement.
nosklo
2
Essayez avec un seul soulignement et vous verrez le résultat que vous obtenez. @nosklo
Billal Begueradj
93

L'expression couramment utilisée est «nous sommes tous des adultes consentants ici». En ajoutant un seul trait de soulignement (ne pas exposer) ou un double trait de soulignement (masquer), vous dites à l'utilisateur de votre classe que vous avez l'intention que le membre soit «privé» d'une manière ou d'une autre. Cependant, vous faites confiance à tous les autres pour qu'ils se comportent de manière responsable et respectent cela, sauf s'ils ont une raison impérieuse de ne pas le faire (par exemple, les débogueurs, la complétion de code).

Si vous devez vraiment avoir quelque chose de privé, vous pouvez l'implémenter dans une extension (par exemple en C pour CPython). Dans la plupart des cas, cependant, vous apprenez simplement la manière de faire les choses en Python.

Tony Meyer
la source
existe-t-il une sorte de protocole wrapper que je suis censé utiliser pour accéder à une variable protégée?
intuition
3
Il n'y a pas plus de variables "protégées" que de "privées". Si vous souhaitez accéder à un attribut commençant par un trait de soulignement, vous pouvez simplement le faire (mais notez que l'auteur décourage cela). Si vous devez accéder à un attribut commençant par un double trait de soulignement, vous pouvez modifier le nom vous-même, mais vous ne voulez certainement pas le faire.
Tony Meyer,
33

Ce n'est pas comme si vous ne pouviez absolument pas contourner la confidentialité des membres dans n'importe quel langage (arithmétique des pointeurs en C ++, Reflections en .NET / Java).

Le fait est que vous obtenez une erreur si vous essayez d'appeler la méthode privée par accident. Mais si vous voulez vous tirer une balle dans le pied, allez-y et faites-le.

Edit: Vous n'essayez pas de sécuriser vos affaires par encapsulation OO, n'est-ce pas?

Maximilian
la source
2
Pas du tout. Je fais simplement remarquer qu'il est étrange de donner au développeur un moyen facile, et à mon avis, magique, d'accéder aux propriétés «privées».
willurd
2
Oui, j'ai juste essayé d'illustrer ce point. Le rendre privé dit simplement "vous ne devriez pas y accéder directement" en faisant se plaindre le compilateur. Mais on veut vraiment vraiment faire ce qu'il peut. Mais oui, c'est plus facile en Python que dans la plupart des autres langages.
Maximilian
7
En Java, vous pouvez réellement sécuriser des éléments via l'encapsulation, mais cela nécessite que vous soyez intelligent et exécutez le code non approuvé dans un SecurityManager, et soyez très prudent. Même Oracle se trompe parfois.
Antimony
12

La class.__stuffconvention de dénomination permet au programmeur de savoir qu'il n'est pas censé accéder__stuff de l'extérieur. Le nom malmené fait qu'il est peu probable que quelqu'un le fasse par accident.

Certes, vous pouvez toujours contourner cela, c'est encore plus facile que dans d'autres langues (ce que BTW vous permet également de faire), mais aucun programmeur Python ne le ferait s'il se soucie de l'encapsulation.

Nickolay
la source
12

Un comportement similaire existe lorsque les noms d'attribut de module commencent par un seul trait de soulignement (par exemple _foo).

Les attributs de module nommés comme tels ne seront pas copiés dans un module d'importation lors de l'utilisation de la from*méthode, par exemple:

from bar import *

Cependant, il s'agit d'une convention et non d'une contrainte de langage. Ce ne sont pas des attributs privés; ils peuvent être référencés et manipulés par tout importateur. Certains soutiennent qu'à cause de cela, Python ne peut pas implémenter une véritable encapsulation.

Ross
la source
12

Ce n'est qu'un de ces choix de conception de langage. À un certain niveau, ils sont justifiés. Ils font en sorte que vous devez aller assez loin pour essayer d'appeler la méthode, et si vous en avez vraiment besoin, vous devez avoir une assez bonne raison!

Les crochets de débogage et les tests viennent à l'esprit comme des applications possibles, utilisées de manière responsable bien sûr.

ctcherry
la source
4

Avec Python 3.4, voici le comportement:

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

Notez que les règles de mutilation sont conçues principalement pour éviter les accidents; il est toujours possible d'accéder ou de modifier une variable considérée comme privée. Cela peut même être utile dans des circonstances spéciales, comme dans le débogueur.

Même si la question est ancienne, j'espère que mon extrait de code pourrait être utile.

Alberto
la source
2

La préoccupation la plus importante concernant les méthodes et les attributs privés est de dire aux développeurs de ne pas l'appeler en dehors de la classe et c'est l'encapsulation. on peut mal comprendre la sécurité de l'encapsulation. quand on utilise délibérément une syntaxe comme celle (ci-dessous) que vous avez mentionnée, vous ne voulez pas d'encapsulation.

obj._MyClass__myPrivateMethod()

J'ai migré de C # et au début, c'était bizarre pour moi aussi, mais après un certain temps, j'ai eu l'idée que seule la façon dont les concepteurs de code Python pensent à la POO est différente.

Afshin Amiri
la source
1

Pourquoi les méthodes «privées» de Python ne sont-elles pas réellement privées?

Si je comprends bien, ils ne peuvent pas être privés. Comment la confidentialité pourrait-elle être respectée?

La réponse évidente est "les membres privés ne sont accessibles que par self", mais cela ne fonctionnerait pas - selfn'est pas spécial en Python, ce n'est rien de plus qu'un nom couramment utilisé pour le premier paramètre d'une fonction.

user200783
la source