¿Atributo “público” o “privado” en Python? ¿Cuál es la mejor manera?

En Python, tengo la siguiente clase de ejemplo:

class Foo: self._attr = 0 @property def attr(self): return self._attr @attr.setter def attr(self, value): self._attr = value @attr.deleter def attr(self): del self._attr 

Como puede ver, tengo un atributo “privado” simple “_attr” y una propiedad para acceder. Hay muchos códigos para declarar un atributo privado simple y creo que no se respeta la filosofía “KISS” para declarar todos los atributos de esa manera.

Entonces, ¿por qué no declarar todos mis atributos como atributos públicos si no necesito un getter / setter / deleter particular?

Mi respuesta será: ¡Porque el principio de encapsulación (OOP) dice lo contrario!

Cuál es la mejor manera ?

Normalmente, el código de Python se esfuerza por cumplir con el principio de acceso uniforme . Específicamente, el enfoque aceptado es:

  • Exponga sus variables de instancia directamente, permitiendo, por ejemplo, foo.x = 0 , no foo.set_x(0)
  • Si necesita ajustar los accesos dentro de los métodos, por cualquier motivo, use @property , que conserva la semántica de acceso. Es decir, foo.x = 0 ahora invoca foo.set_x(0) .

La principal ventaja de este enfoque es que la persona que llama puede hacer esto:

 foo.x += 1 

A pesar de que el código realmente podría estar haciendo:

 foo.set_x(foo.get_x() + 1) 

La primera statement es infinitamente más legible. Sin embargo, con las propiedades, puede agregar (al principio o más adelante) el control de acceso que obtiene con el segundo enfoque.

Tenga en cuenta, también, que las variables de instancia que comienzan con un solo guión bajo son convencionalmente privadas. Es decir, el guión bajo indica a otros desarrolladores que usted considera que el valor es privado y que no deben alterarlo directamente; sin embargo, nada en el lenguaje les impide ensuciarlo directamente.

Si usa un subrayado __x doble (p. Ej., __x ), Python hace un poco de ofuscación al nombre. Sin embargo, se puede acceder a la variable desde fuera de la clase a través de su nombre confuso. No es realmente privado. Es un poco … más opaco. Y hay argumentos válidos contra el uso del doble guión bajo; Por un lado, puede hacer que la depuración sea más difícil.

El prefijo “dunder” (doble guión bajo, __ ) impide el acceso al atributo, excepto a través de los accesores.

 class Foo(): def __init__(self): self.__attr = 0 @property def attr(self): return self.__attr @attr.setter def attr(self, value): self.__attr = value @attr.deleter def attr(self): del self.__attr 

Algunos ejemplos:

 >>> f = Foo() >>> f.__attr # Not directly accessible. Traceback (most recent call last): File "", line 1, in  AttributeError: 'Foo' object has no attribute '__attr' >>> '__attr' in f.__dir__() # Not listed by __dir__() False >>> f.__getattribute__('__attr') # Not listed by __getattribute__() Traceback (most recent call last): File "", line 1, in  AttributeError: 'Foo' object has no attribute '__attr' >>> f.attr # Accessible by implemented getter. 0 >>> f.attr = 'Prest' # Can be set by implemented setter. >>> f.attr 'Prest' >>> f.__attr = 'Tricky?' # Can we set it explicitly? >>> f.attr # No. By doing that we have created a 'Prest' # new but unrelated attribute, same name. 

Sencillamente, los principios de la OOP son erróneos. La razón por la que esto es así es una larga discusión que lleva a los flamewars y es probablemente fuera de tema para este sitio. 🙂

En Python no hay atributos privados, no puedes protegerlos, y esto nunca es un problema real. Así que no lo hagas ¡Fácil! 🙂

Luego viene la pregunta: en caso de tener un guión bajo o no. Y en el ejemplo que tienes aquí definitivamente no deberías. Un guión bajo en Python es una convención para demostrar que algo es interno y no forma parte de la API, y que debe usarlo bajo su propio riesgo. Esto obviamente no es el caso aquí, pero es una convención común y útil.

Python no tiene atributos públicos O privados. Todos los atributos son accesibles a todos los códigos.

 self.attr = 0 #Done 

Tu método no es, de ninguna manera, hacer que _attr sea privado, solo es un poco de ofuscación.

Como han dicho otros, los atributos privados en Python son simplemente una convención. El uso de la syntax de la property debe usarse para un procesamiento especial cuando los atributos se unen, modifican o eliminan. La belleza de Python es que puede comenzar utilizando simplemente el enlace de atributos normales, por ejemplo, self.attr = 0 y si en una fecha posterior decide que desea restringir el valor de attr para decir 0 <= attr <=100 , puede hacer de attr una propiedad y definir un método para asegurarse de que esta condición sea verdadera sin tener que cambiar ningún código de usuario.

Vea este enlace: https://docs.python.org/2/tutorial/classes.html

9.6. Variables privadas y referencias de clase local

Las variables de instancia “privadas” a las que no se puede acceder, excepto desde dentro de un objeto, no existen en Python. Sin embargo, hay una convención seguida por la mayoría del código de Python: un nombre con un guión bajo (p. Ej. _Spam) debe tratarse como una parte no pública de la API (ya sea una función, un método o un miembro de datos) . Debe considerarse un detalle de implementación y sujeto a cambios sin previo aviso.

Dado que existe un caso de uso válido para los miembros de clase privada (es decir, para evitar choques de nombres de nombres con nombres definidos por subclases), existe un soporte limitado para dicho mecanismo, llamado “denominación de nombres”. Cualquier identificador de la forma __spam (al menos dos guiones bajos, a lo sumo un guión bajo) se reemplaza textualmente con _classname__spam, donde nombre de clase es el nombre de la clase actual con el subrayado (s) destacado (s) eliminado (s). Esta mutilación se realiza sin tener en cuenta la posición sintáctica del identificador, siempre que ocurra dentro de la definición de una clase.

Para hacer un atributo privado, solo tienes que hacer self.__attr

 class Foo: self.__attr = 0 @property def attr(self): return self._attr @attr.setter def attr(self, value): self._attr = value @attr.deleter def attr(self): del self._attr 

En Python, a menos que necesite un comportamiento especial fuera de un atributo, no hay necesidad de ocultarlo detrás de los métodos de acceso. Si un atributo es solo para uso interno, prepárelo con un guión bajo.

Lo bueno de las propiedades es que te dieron una interfaz realmente genial para trabajar. A veces es útil derivar una propiedad basada en alguna otra (es decir, el IMC se define por el peso y la altura). El usuario de la interfaz no tiene que saber esto, por supuesto.

Prefiero esta manera a tener getters y setters explícitos como en Java, es decir. Mucho más agradable. 🙂