Cambiando el nombre de Python

109

En otros lenguajes, una pauta general que ayuda a producir un mejor código es siempre hacer que todo esté lo más oculto posible. Si tiene dudas sobre si una variable debe ser privada o protegida, es mejor optar por la privada.

¿Lo mismo ocurre con Python? ¿Debo usar dos guiones bajos iniciales en todo al principio y solo hacerlos menos ocultos (solo un guión bajo) cuando los necesite?

Si la convención es usar solo un guión bajo, también me gustaría conocer el fundamento.

Aquí hay un comentario que dejé sobre la respuesta de JBernardo . Explica por qué hice esta pregunta y también por qué me gustaría saber por qué Python es diferente de los otros lenguajes:

Vengo de lenguajes que te enseñan a pensar que todo debería ser tan público como sea necesario y nada más. El razonamiento es que esto reducirá las dependencias y hará que el código sea más seguro de alterar. La forma de Python de hacer las cosas a la inversa, comenzando desde el público y yendo hacia lo oculto, es extraña para mí.

Paul Manta
fuente

Respuestas:

182

En caso de duda, déjelo en "público", es decir, no agregue nada para ocultar el nombre de su atributo. Si tienes una clase con algún valor interno, no te preocupes por eso. En lugar de escribir:

class Stack(object):

    def __init__(self):
        self.__storage = [] # Too uptight

    def push(self, value):
        self.__storage.append(value)

escribe esto por defecto:

class Stack(object):

    def __init__(self):
        self.storage = [] # No mangling

    def push(self, value):
        self.storage.append(value)

Esta es sin duda una forma controvertida de hacer las cosas. Los novatos de Python simplemente lo odian e incluso algunos viejos de Python desprecian este valor predeterminado, pero de todos modos es el predeterminado, por lo que realmente te recomiendo que lo sigas, incluso si te sientes incómodo.

Si realmente desea enviar el mensaje "¡No puedo tocar esto!" para sus usuarios, la forma habitual es anteponer la variable con un guión bajo. Esto es solo una convención, pero la gente lo entiende y tiene doble cuidado al tratar con tales cosas:

class Stack(object):

    def __init__(self):
        self._storage = [] # This is ok but pythonistas use it to be relaxed about it

    def push(self, value):
        self._storage.append(value)

Esto también puede ser útil para evitar conflictos entre nombres de propiedades y nombres de atributos:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

¿Qué pasa con el doble subrayado? Bueno, la magia de doble subrayado se usa principalmente para evitar la sobrecarga accidental de métodos y conflictos de nombres con los atributos de las superclases. . Puede ser muy útil si escribe una clase que se espera que se amplíe muchas veces.

Si desea utilizarlo para otros fines, puede hacerlo, pero no es ni habitual ni recomendable.

EDITAR : ¿Por qué es así? Bueno, el estilo habitual de Python no enfatiza que las cosas sean privadas, ¡al contrario! Hay muchas razones para eso, la mayoría de ellas controvertidas ... Veamos algunas de ellas.

Python tiene propiedades

La mayoría de los lenguajes orientados a objetos en la actualidad utilizan el enfoque opuesto: lo que no debe usarse no debe ser visible, por lo que los atributos deben ser privados. Teóricamente, esto produciría clases más manejables y menos acopladas, porque nadie cambiaría los valores dentro de los objetos de forma imprudente.

Sin embargo, no es tan sencillo. Por ejemplo, las clases de Java tienen muchos atributos y captadores que solo obtienen los valores y establecedores que solo establecen los valores. Necesita, digamos, siete líneas de código para declarar un solo atributo, lo que un programador de Python diría que es innecesariamente complejo. Además, en la práctica, simplemente escribe todo este código para obtener un campo público, ya que puedes cambiar su valor usando los getters y setters.

Entonces, ¿por qué seguir esta política de privacidad predeterminada? Simplemente haga públicos sus atributos de forma predeterminada. Por supuesto, esto es problemático en Java, porque si decide agregar alguna validación a su atributo, sería necesario que cambie todos

person.age = age;

en su código para, digamos,

person.setAge(age);

setAge() siendo:

public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

Entonces, en Java (y otros lenguajes), el valor predeterminado es usar getters y setters de todos modos, porque pueden ser molestos de escribir pero pueden ahorrarle mucho tiempo si se encuentra en la situación que he descrito.

Sin embargo, no es necesario que lo haga en Python, ya que Python tiene propiedades. Si tienes esta clase:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

y luego decides validar las edades, no necesitas cambiar las person.age = agepartes de tu código. Simplemente agregue una propiedad (como se muestra a continuación)

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

Si puede hacerlo y seguir utilizándolo person.age = age, ¿por qué agregaría campos privados y captadores y definidores?

(Además, consulte Python no es Java y este artículo sobre los daños de usar getters y setters ).

Todo es visible de todos modos, y tratar de esconderse solo complica su trabajo

Incluso en idiomas donde hay atributos privados, puede acceder a ellos a través de algún tipo de biblioteca de reflexión / introspección. Y la gente lo hace mucho, en marcos y para resolver necesidades urgentes. El problema es que las bibliotecas de introspección son solo una forma difícil de hacer lo que podría hacer con los atributos públicos.

Dado que Python es un lenguaje muy dinámico, es contraproducente agregar esta carga a sus clases.

El problema es que no se puede ver, se requiere ver

Para un Pythonista, la encapsulación no es la incapacidad de ver el interior de las clases, sino la posibilidad de evitar mirarlo. Lo que quiero decir es que la encapsulación es la propiedad de un componente que permite su uso sin que el usuario se preocupe por los detalles internos. Si puede usar un componente sin preocuparse por su implementación, entonces está encapsulado (en opinión de un programador de Python).

Ahora, si escribió su clase de tal manera que pueda usarla sin tener que pensar en los detalles de implementación, no hay problema si lo desea. mirar dentro de la clase por alguna razón. El punto es: tu API debería ser buena y el resto son detalles.

Guido lo dijo

Bueno, esto no es controvertido: lo dijo, en realidad . (Busque "kimono abierto").

Esta es la cultura

Sí, hay algunas razones, pero ninguna razón crítica. Este es principalmente un aspecto cultural de la programación en Python. Francamente, también podría ser al revés, pero no es así. Además, podría preguntar fácilmente al revés: ¿por qué algunos lenguajes usan atributos privados de forma predeterminada? Por la misma razón principal que para la práctica de Python: porque es la cultura de estos lenguajes, y cada elección tiene ventajas y desventajas.

Dado que ya existe esta cultura, se recomienda seguirla. De lo contrario, los programadores de Python le molestarán y le pedirán que elimine el__ de su código cuando haga una pregunta en Stack Overflow :)

brandizzi
fuente
1. La encapsulación es para proteger invariantes de clase. No esconder detalles innecesarios al mundo exterior porque sería una molestia. 2. "El punto es: su API debería ser buena y el resto son detalles". Esto es verdad. Y los atributos públicos son parte de su API. Además, a veces los establecedores públicos son apropiados (con respecto a los invariantes de su clase) y otras no lo son. Una API que tiene configuradores públicos que no deberían ser públicos (riesgo de violación de invariantes) es una API incorrecta. Esto significa que tienes que pensar en la visibilidad de cada setter de todos modos y tener un 'predeterminado' significa menos.
Júpiter
21

Primero: ¿Qué es el cambio de nombre?

La alteración de nombres se invoca cuando está en una definición de clase y usa __any_nameo __any_name_, es decir, dos (o más) guiones bajos iniciales y como máximo un guión bajo final.

class Demo:
    __any_name = "__any_name"
    __any_other_name_ = "__any_other_name_"

Y ahora:

>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'

En caso de duda, ¿qué hacer?

El uso ostensible es evitar que los subclasificadores usen un atributo que usa la clase.

Un valor potencial es evitar las colisiones de nombres con los subclasificadores que desean anular el comportamiento, de modo que la funcionalidad de la clase principal siga funcionando como se esperaba. Sin embargo, el ejemplo en la documentación de Python no es sustituible por Liskov, y no me viene a la mente ningún ejemplo en el que lo haya encontrado útil.

Las desventajas son que aumenta la carga cognitiva para leer y comprender una base de código, y especialmente cuando se depura, donde se ve el nombre de subrayado doble en la fuente y un nombre alterado en el depurador.

Mi enfoque personal es evitarlo intencionalmente. Trabajo en una base de código muy grande. Los raros usos de la misma sobresalen como un pulgar dolorido y no parecen justificados.

Debe ser consciente de ello para saberlo cuando lo vea.

PEP 8

PEP 8 , la guía de estilo de la biblioteca estándar de Python, actualmente dice (abreviado):

Existe cierta controversia sobre el uso de __names .

Si su clase está destinada a ser subclasificada y tiene atributos que no desea que utilicen las subclases, considere nombrarlos con guiones bajos iniciales dobles y sin guiones bajos finales.

  1. Tenga en cuenta que solo se usa el nombre de clase simple en el nombre mutilado, por lo que si una subclase elige el mismo nombre de clase y nombre de atributo, aún puede obtener colisiones de nombres.

  2. La alteración de nombres puede hacer que ciertos usos, como la depuración y __getattr__(), sean menos convenientes. Sin embargo, el algoritmo de manipulación de nombres está bien documentado y es fácil de realizar manualmente.

  3. No a todo el mundo le gusta alterar los nombres. Trate de equilibrar la necesidad de evitar conflictos de nombres accidentales con el uso potencial por parte de personas avanzadas.

¿Como funciona?

Si antepone dos guiones bajos (sin terminar los guiones bajos dobles) en una definición de clase, el nombre se alterará y un guión bajo seguido del nombre de la clase se antepondrá al objeto:

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
... 
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

Tenga en cuenta que los nombres solo se alterarán cuando se analice la definición de la clase:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'

Además, aquellos que son nuevos en Python a veces tienen problemas para comprender lo que sucede cuando no pueden acceder manualmente a un nombre que ven definido en una definición de clase. Esta no es una razón sólida en contra, pero es algo a considerar si tiene una audiencia que está aprendiendo.

¿Un subrayado?

Si la convención es usar solo un guión bajo, también me gustaría conocer el fundamento.

Cuando mi intención es que los usuarios mantengan sus manos fuera de un atributo, tiendo a usar solo un guión bajo, pero eso es porque en mi modelo mental, los subclasificadores tendrían acceso al nombre (que siempre tienen, ya que pueden detectar fácilmente el nombre destrozado de todos modos).

Si estuviera revisando el código que usa el __prefijo, preguntaría por qué están invocando la alteración de nombres y si no podrían hacerlo tan bien con un solo guión bajo, teniendo en cuenta que si los subclasificadores eligen los mismos nombres para la clase y class atributo habrá una colisión de nombres a pesar de esto.

Aaron Hall
fuente
15

No diría que la práctica produce un mejor código. Los modificadores de visibilidad solo lo distraen de la tarea en cuestión y, como efecto secundario, obligan a que su interfaz se use como desea. En términos generales, hacer cumplir la visibilidad evita que los programadores estropeen las cosas si no han leído la documentación correctamente.

Una solución mucho mejor es la ruta que fomenta Python: sus clases y variables deben estar bien documentadas y su comportamiento debe ser claro. La fuente debe estar disponible. Esta es una forma mucho más extensible y confiable de escribir código.

Mi estrategia en Python es esta:

  1. Simplemente escriba la maldita cosa, no haga suposiciones sobre cómo deben protegerse sus datos. Esto supone que escribe para crear las interfaces ideales para sus problemas.
  2. Utilice un subrayado inicial para las cosas que probablemente no se usarán externamente y que no forman parte de la interfaz normal de "código de cliente".
  3. Use doble subrayado solo para cosas que sean puramente convenientes dentro de la clase, o que causarán daños considerables si se exponen accidentalmente.

Sobre todo, debe quedar claro lo que hace todo. Documente si alguien más lo usará. Documentelo si quiere que sea útil dentro de un año.

Como nota al margen, en realidad debería optar por la protección en esos otros idiomas: nunca se sabe que su clase podría heredarse más adelante y para qué podría usarse. Lo mejor es proteger solo aquellas variables de las que está seguro que no pueden o no deben ser utilizadas por código externo.

Matt Joiner
fuente
9

No debe comenzar con datos privados y hacerlos públicos según sea necesario. Más bien, debe comenzar por descubrir la interfaz de su objeto. Es decir, debería comenzar por averiguar qué ve el mundo (las cosas públicas) y luego averiguar qué cosas privadas son necesarias para que eso suceda.

Otros lenguajes dificultan hacer privado lo que alguna vez fue público. Es decir, romperé mucho código si hago mi variable privada o protegida. Pero con las propiedades en Python, este no es el caso. Más bien, puedo mantener la misma interfaz incluso reorganizando los datos internos.

La diferencia entre _ y __ es que Python en realidad intenta hacer cumplir este último. Por supuesto, no se esfuerza mucho, pero lo hace difícil. Habiendo _ simplemente les dice a otros programadores cuál es la intención, son libres de ignorar bajo su propio riesgo. Pero ignorar esa regla a veces es útil. Los ejemplos incluyen depuración, piratería temporal y trabajar con código de terceros que no estaba destinado a usarse de la forma en que lo usa.

Winston Ewert
fuente
6

Ya hay muchas buenas respuestas a esto, pero voy a ofrecer otra. Esto también es en parte una respuesta a las personas que siguen diciendo que el doble subrayado no es privado (realmente lo es).

Si observa Java / C #, ambos tienen private / protected / public. Todos estos son construcciones en tiempo de compilación . Solo se aplican en el momento de la compilación. Si tuviera que usar la reflexión en Java / C #, podría acceder fácilmente al método privado.

Ahora, cada vez que llamas a una función en Python, estás usando inherentemente la reflexión. Estos fragmentos de código son los mismos en Python.

lst = []
lst.append(1)
getattr(lst, 'append')(1)

La sintaxis del "punto" es sólo azúcar sintáctica para el último fragmento de código. Principalmente porque usar getattr ya es feo con solo una llamada de función. Simplemente empeora a partir de ahí.

Entonces con eso, no puede haber una versión Java / C # de privado, ya que Python no compila el código. Java y C # no pueden verificar si una función es privada o pública en tiempo de ejecución, ya que esa información se ha ido (y no tiene conocimiento de dónde se llama a la función).

Ahora, con esa información, la alteración del nombre del guión bajo doble tiene más sentido para lograr la "privacidad". Ahora, cuando se llama a una función desde la instancia 'self' y se da cuenta de que comienza con '__', simplemente realiza el cambio de nombre allí mismo. Es simplemente más azúcar sintáctico. Ese azúcar sintáctico permite el equivalente de 'privado' en un lenguaje que solo usa la reflexión para el acceso de miembros de datos.

Descargo de responsabilidad: nunca escuché a nadie del desarrollo de Python decir algo como esto. La verdadera razón de la falta de "privado" es cultural, pero también notará que la mayoría de los lenguajes de secuencias de comandos / interpretados no tienen privado. Un privado estrictamente ejecutable no es práctico en nada excepto en tiempo de compilación.

Jonathan Sternberg
fuente
4

Primero: ¿Por qué quiere ocultar sus datos? ¿Por qué es eso tan importante?

La mayoría de las veces no quieres hacerlo, pero lo haces porque otros lo están haciendo.

Si realmente realmente no quiere que las personas usen algo, agregue uno guión bajo delante. Eso es todo ... Los Pythonistas saben que las cosas con un guión bajo no están garantizadas para funcionar siempre y pueden cambiar sin que usted lo sepa.

Así es como vivimos y estamos de acuerdo con eso.

El uso de dos guiones bajos hará que su clase sea tan mala para la subclase que incluso usted no querrá trabajar de esa manera.

JBernardo
fuente
2
Omitió la razón por la que el subrayado doble es malo para crear subclases ... esto mejoraría su respuesta.
Matt Joiner
2
Dado que los guiones bajos dobles son en realidad solo para evitar colisiones de nombres con subclasificadores (como una forma de decir "manos fuera" a los subclasificadores), no veo cómo la alteración de nombres crea un problema.
Aaron Hall
4

La respuesta elegida hace un buen trabajo al explicar cómo las propiedades eliminan la necesidad de atributos privados , pero también agregaría que las funciones a nivel de módulo eliminan la necesidad de métodos privados .

Si convierte un método en una función a nivel de módulo, elimina la oportunidad de que las subclases lo anulen. Mover alguna funcionalidad al nivel de módulo es más Pythonic que tratar de ocultar métodos con cambios de nombre.

Tanner_Wauchope
fuente
3

El siguiente fragmento de código explicará todos los casos diferentes:

  • dos guiones bajos iniciales (__a)
  • guión bajo inicial único (_a)
  • sin guión bajo (a)

    class Test:
    
    def __init__(self):
        self.__a = 'test1'
        self._a = 'test2'
        self.a = 'test3'
    
    def change_value(self,value):
        self.__a = value
        return self.__a

imprimir todos los atributos válidos del objeto de prueba

testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes

['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a', 
'change_value']

Aquí, puede ver que el nombre de __a se ha cambiado a _Test__a para evitar que esta variable sea anulada por cualquiera de las subclases. Este concepto se conoce como "Cambio de nombres" en Python. Puede acceder a esto de esta manera:

testObj2 = Test()
print testObj2._Test__a

test1

De manera similar, en el caso de _a, la variable es solo para notificar al desarrollador que debe usarse como variable interna de esa clase, el intérprete de Python no hará nada incluso si accedes a ella, pero no es una buena práctica.

testObj3 = Test()
print testObj3._a

test2

se puede acceder a una variable desde cualquier lugar, es como una variable de clase pública.

testObj4 = Test()
print testObj4.a

test3

Espero que la respuesta te haya ayudado :)

Nitish Chauhan
fuente
2

A primera vista debería ser igual que para otros lenguajes (bajo "otros" me refiero a Java o C ++), pero no lo es.

En Java, hizo privadas todas las variables que no deberían ser accesibles desde el exterior. Al mismo tiempo, en Python no puede lograr esto ya que no hay "privacidad" (como dice uno de los principios de Python: "Todos somos adultos"). Entonces, el subrayado doble significa solo "Chicos, no utilicen este campo directamente". El mismo significado tiene un guión bajo, que al mismo tiempo no causa ningún dolor de cabeza cuando tienes que heredar de una clase considerada (solo un ejemplo de un posible problema causado por un guión bajo doble).

Por lo tanto, le recomiendo que utilice un guión bajo único de forma predeterminada para los miembros "privados".

Bodnarchuk romano
fuente
Utilice doble subrayado para "privado" y un subrayado simple para "protegido". Por lo general, la gente solo usa un guión bajo simple para todo (el guión bajo doble ayudará a reforzar la privacidad, que generalmente va en contra del estilo Python).
Jonathan Sternberg
1
¿Pero eso no hace que dos guiones bajos sean similares a privados y uno similar a protegido? ¿Por qué no empezar desde "privado"?
Paul Manta
@Paul No, no es así. No hay privado en Python y no debes intentar lograrlo.
Roman Bodnarchuk
@Roman Conceptualmente hablando ... Fíjese en las comillas alrededor de 'privado'.
Paul Manta
1

"Si tiene dudas sobre si una variable debe ser privada o protegida, es mejor optar por la privada". - sí, lo mismo ocurre en Python.

Algunas respuestas aquí dicen sobre 'convenciones', pero no dan los enlaces a esas convenciones. La guía autorizada para Python, PEP 8 establece explícitamente:

En caso de duda, elija no público; es más fácil hacerlo público más tarde que convertir un atributo público en no público.

La distinción entre público y privado, y la manipulación de nombres en Python se han considerado en otras respuestas. Desde el mismo enlace,

No usamos el término "privado" aquí, ya que ningún atributo es realmente privado en Python (sin una cantidad de trabajo generalmente innecesaria).

Yaroslav Nikitenko
fuente