define - python class attributes
Propiedades de atributo "Privado" en Python (1)
Soy relativamente nuevo en Python, así que espero no haberme perdido algo, pero aquí va ...
Intento escribir un módulo de Python, y me gustaría crear una clase con un atributo "privado" que pueda (o quizás "debería") solo modificarse a través de una o más funciones dentro del módulo. Esto es un esfuerzo para hacer que el módulo sea más robusto, ya que la configuración de este atributo fuera de estas funciones podría conducir a un comportamiento no deseado. Por ejemplo, podría tener:
- Una clase que almacena valores xey para un diagrama de dispersión,
Data
- Una función para leer valores xey de un archivo y almacenarlos en la clase,
read()
- Una función para trazarlos,
plot()
En este caso, preferiría que el usuario no pudiera hacer algo como esto:
data = Data()
read("file.csv", data)
data.x = [0, 3, 2, 6, 1]
plot(data)
Me doy cuenta de que agregar un único guión bajo al nombre indica al usuario que el atributo no debe cambiarse, es decir, cambiar el nombre a _x
y agregar un decorador de propiedades para que el usuario pueda acceder al valor sin sentirse culpable. Sin embargo, ¿y si también quisiera agregar una propiedad setter?
class Data(object):
_x = []
_y = []
@property
def x(self):
return self._x
@x.setter
def x(self, value):
# Do something with value
self._x = value
Ahora estoy en la misma posición que antes: el usuario ya no puede acceder directamente al atributo _x
, pero aún así puede configurarlo usando:
data.x = [0, 3, 2, 6, 1]
Idealmente cambiaría el nombre de las definiciones de la función de propiedad a _x()
, pero esto genera confusión sobre lo que realmente significa self._x
(dependiendo del orden en que se declaran, esto parece dar como resultado que el setter sea llamado recursivamente o setter siendo ignorado a favor del atributo).
Un par de soluciones que puedo pensar:
- Agregue un guion bajo
__x
doble al atributo,__x
, para que el nombre se trunque y no se confunda con la función del colocador. Según tengo entendido, esto debe reservarse para los atributos que una clase no desea compartir con posibles subclases, por lo que no estoy seguro de si se trata de un uso legítimo. - Cambie el nombre del atributo, por ejemplo
_x_stored
. Si bien esto resuelve el problema por completo, hace que el código sea más difícil de leer e introduce problemas de convención de nombres. ¿A qué atributos cambio el nombre? solo los que son relevantes? solo los que tienen propiedades? solo los de esta clase?
¿Son aplicables alguna de las soluciones anteriores? Y si no, ¿hay una mejor manera de resolver este problema?
Editar
Gracias por las respuestas hasta el momento. Algunos puntos arrojados por los comentarios:
- Quiero conservar la lógica extra que me brinda la propiedad setter - la sección
# Do something with value
en el ejemplo anterior - por lo que configurar internamente el atributo a través del acceso directo deself._x
no resuelve el problema. - Eliminar la propiedad setter y crear una función separada
_set_x()
resuelve el problema, pero no es una solución muy ordenada, ya que permite establecer_x
de dos maneras diferentes, ya sea llamando a esa función o a través del acceso directo deself._x
. Tendría que hacer un seguimiento de los atributos que deben establecerse por su propia función de establecimiento (no propiedad) y que deben modificarse a través del acceso directo. Probablemente preferiría usar una de las soluciones que sugerí anteriormente, porque aunque hacen un lío con las convenciones de nomenclatura dentro de la clase, al menos son consistentes en su uso fuera de la clase, es decir, todas usan el azúcar sintáctico de las propiedades. . Si no hay manera de hacer esto de una manera más ordenada, entonces supongo que solo tengo que elegir la que causa la menor interrupción.
Si desea disuadir a los usuarios de cambiar una propiedad, pero quiere que quede claro que pueden leerla, usaría @property
sin proporcionar un setter, similar a lo que describió anteriormente:
class Data(object):
def __init__(self):
self._x = []
self._y = []
@property
def x(self):
return self._x
@property
def y(self):
return self._x
Sé que mencionas "¿Y si quisiera agregar un colocador a la propiedad?", Pero supongo que lo contrarrestaría con: ¿Por qué agregar el colocador si no quieres que tus clientes puedan establecer la propiedad? Internamente, puede acceder a self._x
directamente.
En cuanto a un cliente que accede directamente a _x
o _y
, cualquier variable con un prefijo ''_'' se considera "privada" en Python, por lo que debe confiar en que sus clientes obedezcan. Si no obedecen eso, y terminan arruinando cosas, es su propia culpa. Este tipo de mentalidad es contraria a muchos otros lenguajes (C ++, Java, etc.) donde se considera que mantener los datos privados es muy importante, pero la cultura de Python es simplemente diferente a este respecto.
Editar
Otra nota, ya que sus propiedades privadas en este caso particular son listas, que son mutables (a diferencia de las cadenas o ints, que son inmutables), un cliente podría terminar cambiándolos de forma accidental:
>>> d = Data()
>>> print d.x
[''1'', ''2'']
>>> l = d.x
>>> print l
[''1'', ''2'']
>>> l.append("3")
>>> print d.x
[''1'', ''2'', ''3''] # Oops!
Si quiere evitar esto, necesitará su propiedad para devolver una copia de la lista:
@property
def x(self):
return list(self._x)