¿Por qué los métodos 'privados' de Python no son realmente privados?

658

Python nos da la capacidad de crear métodos 'privados' y variables dentro de una clase anteponiendo doble subrayado al nombre, así: __myPrivateMethod(). ¿Cómo, entonces, se puede explicar esto?

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

¡¿Cual es el trato?!

Explicaré esto un poco para aquellos que no lo entendieron.

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

Lo que hice allí fue crear una clase con un método público y un método privado e instanciarlo.

A continuación, llamo a su método público.

>>> obj.myPublicMethod()
public method

A continuación, intento llamar a su método privado.

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

Todo se ve bien aquí; No podemos llamarlo. Es, de hecho, 'privado'. Bueno, en realidad no lo es. Ejecutar dir () en el objeto revela un nuevo método mágico que Python crea mágicamente para todos sus métodos 'privados'.

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

El nombre de este nuevo método siempre es un guión bajo, seguido del nombre de la clase, seguido del nombre del método.

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

Demasiado encapsulado, ¿eh?

En cualquier caso, siempre escuché que Python no admite la encapsulación, entonces, ¿por qué intentarlo? ¿Lo que da?

willurd
fuente
18
Lo mismo es cierto para Java o C # si usa la reflexión (que de alguna manera es lo que está haciendo allí).
0x434D53
44
Fue creado para el propósito de Prueba de Unidad, por lo que puede usar ese "hack" para probar la unidad de los métodos privados de su clase desde afuera.
waas1919
16
¿No es la prueba de métodos privados un antipatrón? Los métodos privados se usarán en algún método público para asegurarse de que no se usen para siempre. Y la forma correcta de probar métodos privados (basado en mi aprendizaje hasta ahora de ThoughtWorks) es escribir pruebas para métodos públicos que cubran todos los casos. Si eso funciona bien, no necesita probar métodos privados desde afuera.
Vishnu Narang
3
@VishnuNarang: Sí, eso es lo que a menudo se enseña. Pero como siempre, un enfoque casi "religioso" de " siempre hacer esto, nunca hacer eso" es lo único que "nunca" es bueno. Si las pruebas unitarias se usan "solo" para pruebas de regresión o pruebas de API pública, no es necesario que pruebe las privadas. Pero si realiza un desarrollo impulsado por pruebas unitarias, hay buenas razones para probar métodos privados durante el desarrollo (por ejemplo, cuando es difícil burlarse de ciertos parámetros inusuales / extremos a través de la interfaz pública). Algunos entornos de prueba de idiomas / unidades no le permiten hacer esto, lo que en mi humilde opinión no es bueno.
Marco Freudenberger
55
@MarcoFreudenberger Veo tu punto. Tengo experiencia en el desarrollo impulsado por pruebas unitarias. A menudo, cuando se hace difícil burlarse de los parámetros, la mayoría de las veces se resuelve cambiando y mejorando el diseño. Todavía tengo que encontrarme con un escenario en el que el diseño es perfecto y aún así las pruebas unitarias son extremadamente difíciles de evitar probar métodos privados. Me ocuparé de tales casos. Gracias. Te agradecería si pudieras compartir un escenario fuera de tu cabeza para ayudarme a entender.
Vishnu Narang

Respuestas:

592

La codificación del nombre se utiliza para garantizar que las subclases no anulen accidentalmente los métodos y atributos privados de sus superclases. No está diseñado para evitar el acceso deliberado desde el exterior.

Por ejemplo:

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

Por supuesto, se descompone si dos clases diferentes tienen el mismo nombre.

Alya
fuente
12
docs.python.org/2/tutorial/classes.html . Sección: 9.6 sobre variables privadas y referencias de clase local.
gjain
72
Para aquellos de nosotros que somos demasiado flojos para desplazarnos / buscar: Sección 9.6 enlace directo
cod3monk3y
3
Debe poner un solo guión bajo para especificar que la variable debe considerarse como privada. Nuevamente, esto no impide que alguien acceda realmente a eso.
igon
14
Guido respondió a esta pregunta : "la razón principal para hacer que (casi) todo sea reconocible fue la depuración: cuando se realiza la depuración, a menudo es necesario romper las abstracciones". Lo agregué como comentario porque es demasiado tarde, demasiadas respuestas.
Peter M. - representa a Mónica
1
Si sigue el criterio de "evitar el acceso deliberado", la mayoría de los idiomas de OOP no admiten miembros verdaderamente privados. Por ejemplo, en C ++ tiene acceso sin procesar a la memoria y en C # el código confiable puede usar la reflexión privada.
CodesInChaos
207

Ejemplo de función privada.

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
fuente
12
self = MyClass() self.private_function(). : D Por supuesto que no funciona en clases, pero solo tienes que definir una función personalizada: def foo(self): self.private_function()
Casey Kuball
161
Por si acaso no estaba claro: nunca haga esto en código real;)
Sudo Bash
10
@ThorSummoner O simplemente function_call.startswith('self.').
nyuszika7h
13
inspect.stack()[1][4][0].strip()<- ¿Cuáles son esos números mágicos 1, 4 y 0?
akhy
55
Esto puede ser derrotado fácilmente self = MyClass(); self.private_function()y falla cuando se llama usando x = self.private_function()dentro de un método.
Será el
171

Cuando llegué por primera vez de Java a Python, odiaba esto. Me dio mucho miedo.

Hoy podría ser lo único que más me gusta de Python.

Me encanta estar en una plataforma, donde las personas confían unas en otras y no sienten que necesitan construir muros impenetrables alrededor de su código. En lenguajes fuertemente encapsulados, si una API tiene un error y ha descubierto lo que sale mal, es posible que aún no pueda solucionarlo porque el método necesario es privado. En Python la actitud es: "seguro". Si crees que entiendes la situación, quizás incluso la hayas leído, entonces todo lo que podemos decir es "¡buena suerte!"

Recuerde, la encapsulación ni siquiera está débilmente relacionada con la "seguridad" o con mantener a los niños fuera del césped. Es solo otro patrón que debe usarse para hacer que una base de código sea más fácil de entender.

Thomas Ahle
fuente
36
@CamJackson Javascript es tu ejemplo? ¿El único lenguaje ampliamente utilizado que tiene una herencia basada en prototipos y un lenguaje que favorece la programación funcional? Creo que JS es mucho más difícil de aprender que la mayoría de los otros idiomas, ya que toma varios pasos ortogonales de la OOP tradicional. No es que esto evite que los idiotas escriban JS, simplemente no lo saben;)
K.Steff
23
Las API son realmente un buen ejemplo de por qué es importante la encapsulación y cuándo se preferirían los métodos privados. Un método destinado a ser privado puede desaparecer, cambiar la firma o, lo que es peor, cambiar el comportamiento, todo sin previo aviso, en cualquier nueva versión posterior. ¿Su miembro inteligente y adulto del equipo realmente recordará que accedió a un método destinado a ser privado dentro de un año cuando actualiza? ¿Estará ella trabajando allí más?
inocente
2
No estoy de acuerdo con el argumento. En el código de producción, lo más probable es que nunca use una API que tenga un error que me haga cambiar miembros públicos para que "funcione". Una API debería funcionar. Si no es así, presentaría un informe de error o haría la misma API yo mismo. No me gusta la filosofía y no me gusta mucho Python, aunque su sintaxis hace que sea divertido escribir guiones más pequeños en ...
Yngve Sneen Lindal
44
Java tiene Method.setAccessible y Field.setAccessible. También miedo?
Tony
15
La aplicación en Java y C ++ no se debe a que Java desconfíe del usuario mientras que Python sí. Esto se debe a que el compilador y / o VM pueden hacer varias suposiciones cuando se trata de cómo funciona su negocio si conoce esta información, por ejemplo, C ++ puede omitir una capa completa de indirección mediante el uso de llamadas C antiguas regulares en lugar de llamadas virtuales, y eso importa cuando trabajas en cosas de alto rendimiento o alta precisión. Python por su naturaleza realmente no puede hacer un buen uso de la información sin comprometer su dinamismo. Ambos idiomas apuntan a cosas diferentes, por lo que ninguno de ellos está "equivocado"
Shayne
144

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

Estrictamente hablando, los métodos privados son accesibles fuera de su clase, pero no son fácilmente accesibles. Nada en Python es verdaderamente privado; internamente, los nombres de los métodos y atributos privados se destrozan y desorganizan sobre la marcha para que parezcan inaccesibles por sus nombres de pila. Puede acceder al método __parse de la clase MP3FileInfo con el nombre _MP3FileInfo__parse. Reconozca que esto es interesante, luego prometa que nunca, nunca lo hará en código real. Los métodos privados son privados por una razón, pero como muchas otras cosas en Python, su privacidad es en última instancia una cuestión de convención, no de fuerza.

xsl
fuente
202
o como dijo Guido van Rossum: "todos somos adultos".
35
-1: esto está mal. Los guiones bajos dobles nunca deben usarse como privados en primer lugar. La respuesta de Alya a continuación dice la verdadera intención de la sintaxis de cambio de nombre. La convención real es un solo guión bajo.
nosklo
2
Pruebe solo con un guión bajo y verá el resultado que obtiene. @nosklo
Billal Begueradj
93

La frase que se usa comúnmente es "aquí todos aceptamos adultos". Al anteponer un solo guión bajo (no exponer) o doble guión bajo (ocultar), le está diciendo al usuario de su clase que pretende que el miembro sea 'privado' de alguna manera. Sin embargo, confía en que todos los demás se comporten de manera responsable y respeten eso, a menos que tengan una razón convincente para no hacerlo (por ejemplo, depuradores, finalización de código).

Si realmente debe tener algo privado, puede implementarlo en una extensión (por ejemplo, en C para CPython). Sin embargo, en la mayoría de los casos, simplemente aprende la forma pitónica de hacer las cosas.

Tony Meyer
fuente
Entonces, ¿hay algún tipo de protocolo contenedor que se supone que debo usar para acceder a una variable protegida?
intuido
3
No hay variables "protegidas" como tampoco hay "privadas". Si desea acceder a un atributo que comienza con un guión bajo, puede hacerlo (pero tenga en cuenta que el autor lo desaconseja). Si debe acceder a un atributo que comienza con un doble guión bajo, puede cambiar el nombre usted mismo, pero es casi seguro que no quiere hacerlo.
Tony Meyer
33

No es que usted no pueda evitar la privacidad de los miembros en ningún lenguaje (aritmética de puntero en C ++, Reflexiones en .NET / Java).

El punto es que obtiene un error si intenta llamar al método privado por accidente. Pero si quieres dispararte en el pie, adelante y hazlo.

Editar: No intentas asegurar tus cosas mediante la encapsulación OO, ¿verdad?

Maximiliano
fuente
2
De ningún modo. Simplemente estoy señalando que es extraño darle al desarrollador una forma fácil y, en mi opinión, mágica de acceder a propiedades 'privadas'.
willurd
2
Sí, solo traté de ilustrar el punto. Hacerlo privado solo dice "no deberías acceder a esto directamente" haciendo que el compilador se queje. Pero uno realmente quiere hacerlo, realmente puede. Pero sí, es más fácil en Python que en la mayoría de los otros idiomas.
Maximiliano
77
En Java, en realidad puede asegurar cosas a través de la encapsulación, pero eso requiere que sea inteligente y ejecute el código no confiable en un SecurityManager, y tenga mucho cuidado. Incluso Oracle se equivoca a veces.
Antimonio
12

La class.__stuffconvención de nomenclatura le permite al programador saber que no debe acceder __stuffdesde afuera. El cambio de nombre hace improbable que alguien lo haga por accidente.

Es cierto que aún puede solucionar esto, es incluso más fácil que en otros lenguajes (que por cierto también le permite hacer esto), pero ningún programador de Python haría esto si se preocupa por la encapsulación.

Nickolay
fuente
12

Un comportamiento similar existe cuando los nombres de los atributos del módulo comienzan con un guión bajo (por ejemplo, _foo).

Los atributos del módulo nombrados como tales no se copiarán en un módulo de importación cuando se utilice el from*método, por ejemplo:

from bar import *

Sin embargo, esta es una convención y no una restricción de idioma. Estos no son atributos privados; pueden ser referenciados y manipulados por cualquier importador. Algunos sostienen que debido a esto, Python no puede implementar una verdadera encapsulación.

Ross
fuente
12

Es solo una de esas opciones de diseño de lenguaje. En cierto nivel están justificados. Lo hacen para que tengas que esforzarte mucho para intentar llamar al método, y si realmente lo necesitas tanto, ¡debes tener una buena razón!

Los ganchos de depuración y las pruebas vienen a la mente como posibles aplicaciones, por supuesto, utilizadas de manera responsable.

ctcherry
fuente
4

Con Python 3.4 este es el comportamiento:

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

Tenga en cuenta que las reglas de destrucción están diseñadas principalmente para evitar accidentes; aún es posible acceder o modificar una variable que se considera privada. Esto incluso puede ser útil en circunstancias especiales, como en el depurador.

Incluso si la pregunta es antigua, espero que mi fragmento pueda ser útil.

Alberto
fuente
2

La preocupación más importante sobre los métodos y atributos privados es decirles a los desarrolladores que no lo llamen fuera de la clase y esto es encapsulación. uno puede malinterpretar la seguridad de la encapsulación. cuando uno deliberadamente usa una sintaxis como esa (abajo) que mencionó, no desea la encapsulación.

obj._MyClass__myPrivateMethod()

He migrado de C # y al principio también fue extraño para mí, pero después de un tiempo llegué a la idea de que solo la forma en que los diseñadores de código de Python piensan sobre OOP es diferente.

Afshin Amiri
fuente
1

¿Por qué los métodos 'privados' de Python no son realmente privados?

Según tengo entendido, no pueden ser privados. ¿Cómo se puede hacer cumplir la privacidad?

La respuesta obvia es "solo se puede acceder a los miembros privados self", pero eso no funcionaría: selfno es especial en Python, no es más que un nombre de uso común para el primer parámetro de una función.

usuario200783
fuente