Propiedad de solo lectura de Python

No sé cuándo el atributo debe ser privado y si debo usar la propiedad.

Leí recientemente que los setters y los getters no son pythonic y debo usar el decorador de propiedades Está bien.

Pero qué sucede si tengo un atributo, eso no debe establecerse fuera de la clase sino que puede leerse (atributo de solo lectura). ¿Debería este atributo ser privado, y por privado quiero decir con subrayado, como ese self._x ? Si es así, ¿cómo puedo leerlo sin usar getter? El único método que conozco ahora es escribir

 @property def x(self): return self._x 

De esa manera puedo leer el atributo por obj.x pero no puedo establecerlo obj.x = 1 así que está bien.

Pero, ¿debería realmente importarme establecer un objeto que no se debe configurar? Tal vez debería dejarlo. Pero, de nuevo, no puedo usar el guión bajo porque leer obj._x es extraño para el usuario, por lo que debería usar obj.x y, de nuevo, el usuario no sabe que no debe establecer este atributo.

¿Cuál es tu opinión y prácticas?

En general, los progtwigs de Python deben escribirse con el supuesto de que todos los usuarios están consintiendo adultos y, por lo tanto, son responsables de usar las cosas correctamente. Sin embargo, en el raro caso de que simplemente no tenga sentido que un atributo sea configurable (como un valor derivado o un valor leído de alguna fuente de datos estática), la propiedad solo de captador es generalmente el patrón preferido.

Solo mis dos centavos, Silas Ray va por el buen camino, sin embargo, me dio ganas de agregar un ejemplo. 😉

Python es un lenguaje de tipo inseguro y, por lo tanto, siempre tendrá que confiar en los usuarios de su código para usar el código como una persona razonable (sensible).

Por PEP 8 :

Utilice un guión bajo solo para métodos no públicos y variables de instancia.

Para tener una propiedad de ‘solo lectura’ en una clase, puede hacer uso de la decoración de @property , tendrá que heredar de un object cuando lo haga para usar las clases de nuevo estilo.

Ejemplo:

 >>> class A(object): ... def __init__(self, a): ... self._a = a ... ... @property ... def a(self): ... return self._a ... >>> a = A('test') >>> aa 'test' >>> aa = 'pleh' Traceback (most recent call last): File "", line 1, in  AttributeError: can't set attribute 

Aquí hay una manera de evitar el supuesto de que

todos los usuarios son adultos que consienten, y por lo tanto son responsables de usar las cosas correctamente por sí mismos.

por favor vea mi actualización a continuación

Usar @property , es muy detallado, por ejemplo:

  class AClassWithManyAttributes: '''refactored to properties''' def __init__(a, b, c, d, e ...) self._a = a self._b = b self._c = c self.d = d self.e = e @property def a(self): return self._a @property def b(self): return self._b @property def c(self): return self._c # you get this ... it's long 

Utilizando

No hay subrayado: es una variable pública.
Un subrayado: es una variable protegida.
Dos guiones bajos: es una variable privada.

Excepto el último, es una convención. Aún puede, si realmente lo intenta, acceder a las variables con doble guión bajo.

¿Asi que que hacemos? ¿Nos rendimos al tener propiedades de solo lectura en Python?

¡Ay, read_only_properties decorador al rescate!

 @read_only_properties('readonly', 'forbidden') class MyClass(object): def __init__(self, a, b, c): self.readonly = a self.forbidden = b self.ok = c m = MyClass(1, 2, 3) m.ok = 4 # we can re-assign a value to m.ok # read only access to m.readonly is OK print(m.ok, m.readonly) print("This worked...") # this will explode, and raise AttributeError m.forbidden = 4 

Usted pregunta:

¿De read_only_properties viene read_only_properties ?

Me alegra que hayas preguntado, aquí está la fuente de read_only_properties :

 def read_only_properties(*attrs): def class_rebuilder(cls): "The class decorator" class NewClass(cls): "This is the overwritten class" def __setattr__(self, name, value): if name not in attrs: pass elif name not in self.__dict__: pass else: raise AttributeError("Can't modify {}".format(name)) super().__setattr__(name, value) return NewClass return class_rebuilder 

actualizar

Nunca esperé que esta respuesta reciba tanta atención. Sorprendentemente lo hace. Esto me animó a crear un paquete que pueda usar.

 $ pip install read-only-properties 

en su shell de python:

 In [1]: from rop import read_only_properties In [2]: @read_only_properties('a') ...: class Foo: ...: def __init__(self, a, b): ...: self.a = a ...: self.b = b ...: In [3]: f=Foo('explodes', 'ok-to-overwrite') In [4]: fb = 5 In [5]: fa = 'boom' --------------------------------------------------------------------------- AttributeError Traceback (most recent call last)  in () ----> 1 fa = 'boom' /home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value) 116 pass 117 else: --> 118 raise AttributeError("Can't touch {}".format(name)) 119 120 super().__setattr__(name, value) AttributeError: Can't touch a 

Aquí hay un enfoque ligeramente diferente para las propiedades de solo lectura, que quizás deberían llamarse propiedades de una sola escritura, ya que tienen que inicializarse, ¿no es así? Para los paranoicos entre nosotros que se preocupan por poder modificar las propiedades al acceder directamente al diccionario del objeto, introduje la manipulación de nombres “extrema”:

 from uuid import uuid4 class Read_Only_Property: def __init__(self, name): self.name = name self.dict_name = uuid4().hex self.initialized = False def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.dict_name] def __set__(self, instance, value): if self.initialized: raise AttributeError("Attempt to modify read-only property '%s'." % self.name) instance.__dict__[self.dict_name] = value self.initialized = True class Point: x = Read_Only_Property('x') y = Read_Only_Property('y') def __init__(self, x, y): self.x = x self.y = y if __name__ == '__main__': try: p = Point(2, 3) print(px, py) px = 9 except Exception as e: print(e) 

Tenga en cuenta que los métodos de instancia también son atributos (de la clase) y que podría establecerlos a nivel de clase o instancia si realmente quisiera ser un tipo duro. O que puede establecer una variable de clase (que también es un atributo de la clase), donde las propiedades de solo lectura no funcionarán de forma ordenada. Lo que trato de decir es que el problema del “atributo de solo lectura” es de hecho más general de lo que generalmente se percibe. Afortunadamente, existen expectativas convencionales en el trabajo que son tan fuertes que nos ciegan en estos otros casos (después de todo, casi todo es un atributo de algún tipo en Python).

Sobre la base de estas expectativas, creo que el enfoque más general y liviano es adoptar la convención de que los atributos “públicos” (sin subrayado destacado) son de solo lectura, excepto cuando se documenta explícitamente como escribible. Esto incluye la expectativa habitual de que los métodos no serán parcheados y las variables de clase que indican los valores predeterminados de la instancia son mucho mejores. Si se siente realmente paranoico con respecto a algún atributo especial, use un descriptor de solo lectura como última medida de recursos.

Si bien me gusta el decorador de clase de Oz123, también puede hacer lo siguiente, que utiliza un envoltorio de clase explícito y __nuevo__ con un método de Fábrica de clase que devuelve la clase dentro de un cierre:

 class B(object): def __new__(cls, val): return cls.factory(val) @classmethod def factory(cls, val): private = {'var': 'test'} class InnerB(object): def __init__(self): self.variable = val pass @property def var(self): return private['var'] return InnerB() 

Esa es mi solución.

 @property def language(self): return self._language @language.setter def language(self, value): # WORKAROUND to get a "getter-only" behavior # set the value only if the attribute does not exist try: if self.language == value: pass print("WARNING: Cannot set attribute \'language\'.") except AttributeError: self._language = value 

Sé que estoy recuperando este hilo, pero estaba viendo cómo hacer una propiedad de solo lectura y después de encontrar este tema, no estaba satisfecho con las soluciones que ya había compartido.

Entonces, volviendo a la pregunta inicial, si comienza con este código:

 @property def x(self): return self._x 

Y si quieres hacer que X sea de solo lectura, puedes agregar:

 @x.setter def x(self, value): raise Exception("Member readonly") 

Entonces, si ejecuta lo siguiente:

 print (x) # Will print whatever X value is x = 3 # Will raise exception "Member readonly"