color - python plotly axis
¿Un ejemplo del mundo real sobre cómo usar la función de propiedad en python? (11)
Estoy interesado en cómo usar @property
en Python. He leído los documentos de Python y el ejemplo allí, en mi opinión, es solo un código de juguete:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I''m the ''x'' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
No sé qué beneficio (s) puedo obtener al envolver el _x
lleno con el decorador de la propiedad. ¿Por qué no simplemente implementar como:
class C(object):
def __init__(self):
self.x = None
Creo que la característica de propiedad podría ser útil en algunas situaciones. ¿Pero cuando? ¿Podría alguien darme algunos ejemplos del mundo real?
Gracias.
Al leer las respuestas y los comentarios, el tema principal parece ser que las respuestas parecen estar perdiendo un ejemplo simple pero útil. He incluido uno muy simple aquí que demuestra el uso simple del decorador @property
. Es una clase que permite a un usuario especificar y obtener una medición de distancia usando una variedad de unidades diferentes, es decir, in_feet
o in_metres
.
class Distance(object):
def __init__(self):
# This private attribute will store the distance in metres
# All units provided using setters will be converted before
# being stored
self._distance = 0.0
@property
def in_metres(self):
return self._distance
@in_metres.setter
def in_metres(self, val):
try:
self._distance = float(val)
except:
raise ValueError("The input you have provided is not recognised "
"as a valid number")
@property
def in_feet(self):
return self._distance * 3.2808399
@in_feet.setter
def in_feet(self, val):
try:
self._distance = float(val) / 3.2808399
except:
raise ValueError("The input you have provided is not recognised "
"as a valid number")
@property
def in_parsecs(self):
return self._distance * 3.24078e-17
@in_parsecs.setter
def in_parsecs(self, val):
try:
self._distance = float(val) / 3.24078e-17
except:
raise ValueError("The input you have provided is not recognised "
"as a valid number")
Uso:
>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14
Algo que muchos no notan al principio es que puedes crear tus propias subclases de propiedades. Esto lo he encontrado muy útil para exponer atributos de solo lectura de objetos o atributos que puedes leer y escribir pero no eliminar. También es una excelente manera de ajustar la funcionalidad, como las modificaciones de seguimiento en los campos de objetos.
class reader(property):
def __init__(self, varname):
_reader = lambda obj: getattr(obj, varname)
super(reader, self).__init__(_reader)
class accessor(property):
def __init__(self, varname, set_validation=None):
_reader = lambda obj: getattr(obj, varname)
def _writer(obj, value):
if set_validation is not None:
if set_validation(value):
setattr(obj, varname, value)
super(accessor, self).__init__(_reader, _writer)
#example
class MyClass(object):
def __init__(self):
self._attr = None
attr = reader(''_attr'')
Eche un vistazo a este artículo para un uso muy práctico. En resumen, explica cómo en Python puede eliminar el método getter / setter explícito, ya que si llega a necesitarlos en algún momento puede usar la property
para una implementación fluida.
La propiedad es solo una abstracción alrededor de un campo que le da más control sobre las formas en que un campo específico puede ser manipulado y para hacer cálculos de middleware. Pocos de los usos que se me ocurren son la validación, la inicialización previa y la restricción de acceso.
@property
def x(self):
"""I''m the ''x'' property."""
if self._x is None:
self._x = Foo()
return self._x
La respuesta breve a su pregunta es que, en su ejemplo, no hay ningún beneficio. Probablemente deberías usar el formulario que no involucra propiedades.
La razón por la que existen las propiedades es que si su código cambia en el futuro, y de repente necesita hacer más con sus datos: valores de caché, proteger el acceso, consultar algún recurso externo ... lo que sea, puede modificar fácilmente su clase para agregar getters y configuradores de los datos sin cambiar la interfaz, por lo que no tiene que encontrar en todas partes en su código donde se accede a esos datos y cambiar eso también.
Otra buena característica de las propiedades sobre el uso de setters y getters es que le permiten continuar utilizando OP = operadores (por ejemplo, + =, - =, * = etc) en sus atributos sin perder la validación, el control de acceso, el almacenamiento en caché, etc. los setters y getters suministrarían.
por ejemplo, si escribió la clase Person
con un setage(newage)
y un getter getage()
, luego para incrementar la edad que tendría que escribir:
bob = Person(''Robert'', 25)
bob.setage(bob.getage() + 1)
pero si convirtieras la age
una propiedad podrías escribir la más limpia:
bob.age += 1
Otros ejemplos serían la validación / filtrado de los atributos establecidos (forzándolos a estar dentro de los límites o aceptable) y la evaluación lenta de términos complejos o que cambian rápidamente.
Cálculo complejo escondido detrás de un atributo:
class PDB_Calculator(object):
...
@property
def protein_folding_angle(self):
# number crunching, remote server calls, etc
# all results in an angle set in ''some_angle''
# It could also reference a cache, remote or otherwise,
# that holds the latest value for this angle
return some_angle
>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276
Validación:
class Pedometer(object)
...
@property
def stride_length(self):
return self._stride_length
@stride_length.setter
def stride_length(self, value):
if value > 10:
raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
else:
self._stride_length = value
Python tiene un gran concepto llamado propiedad que hace que la vida de un programador orientado a objetos sea mucho más simple.
Antes de definir y entrar en detalles sobre qué es @property, primero construyamos una intuición sobre por qué sería necesario en primer lugar.
Un ejemplo para comenzar Supongamos que decides hacer una clase que pueda almacenar la temperatura en grados Celsius. También implementaría un método para convertir la temperatura en grados Fahrenheit. Una forma de hacerlo es la siguiente.
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
Podríamos hacer objetos de esta clase y manipular la temperatura del atributo como lo deseáramos. Pruebe esto en el shell de Python.
>>> # create new object
>>> man = Celsius()
>>> # set temperature
>>> man.temperature = 37
>>> # get temperature
>>> man.temperature
37
>>> # get degrees Fahrenheit
>>> man.to_fahrenheit()
98.60000000000001
Los decimales adicionales al convertir a Fahrenheit se deben al error aritmético de punto flotante (pruebe con 1.1 + 2.2 en el intérprete de Python).
Cada vez que asignamos o recuperamos cualquier atributo de objeto como temperatura, como se muestra arriba, Python lo busca en el diccionario dict del objeto.
>>> man.__dict__
{''temperature'': 37}
Por lo tanto, la temperatura humana se convierte internamente en hombre. dict [''temperatura''].
Ahora, asumamos que nuestra clase se hizo popular entre los clientes y comenzaron a usarla en sus programas. Hicieron todo tipo de asignaciones al objeto.
Un día fatídico, un cliente de confianza vino a nosotros y sugirió que las temperaturas no pueden bajar de -273 grados Celsius (los estudiantes de termodinámica podrían argumentar que en realidad es -273.15), también llamado el cero absoluto. Luego nos pidió que implementamos esta restricción de valor. Siendo una compañía que lucha por la satisfacción del cliente, seguimos la sugerencia y lanzamos la versión 1.01 (una actualización de nuestra clase existente).
Uso de Getters y Setters Una solución obvia a la restricción anterior será ocultar la temperatura del atributo (hacerlo privado) y definir nuevas interfaces getter y setter para manipularlo. Esto puede hacerse de la siguiente manera.
class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# new update
def get_temperature(self):
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
self._temperature = value
Podemos ver más arriba que se definieron los nuevos métodos get_temperature () y set_temperature () y, además, la temperatura se reemplazó con la temperatura. Un guión bajo ( ) al comienzo se usa para denotar variables privadas en Python.
>>> c = Celsius(-277)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible
>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)
>>> c.set_temperature(-300)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible
Esta actualización implementó con éxito la nueva restricción. Ya no podemos configurar la temperatura por debajo de -273.
Tenga en cuenta que las variables privadas no existen en Python. Simplemente hay normas a seguir. El idioma en sí no aplica ninguna restricción.
>>> c._temperature = -300
>>> c.get_temperature()
-300
Pero esto no es de gran preocupación. El gran problema con la actualización anterior es que todos los clientes que implementaron nuestra clase anterior en su programa tienen que modificar su código de obj.temperature a obj.get_temperature () y todas las asignaciones como obj.temperature = val a obj.set_temperature ( val).
Esta refactorización puede causar dolores de cabeza a los clientes con cientos de miles de líneas de códigos.
En general, nuestra nueva actualización no era compatible con versiones anteriores. Aquí es donde la propiedad viene a rescatar.
El poder de @property La manera pitónica de lidiar con el problema anterior es usar la propiedad. Así es como podríamos haberlo logrado.
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
def get_temperature(self):
print("Getting value")
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value
temperature = property(get_temperature,set_temperature)
Y emita el siguiente código en shell una vez que lo ejecute.
>>> c = Celsius()
Añadimos una función print () dentro de get_temperature () y set_temperature () para observar claramente que se están ejecutando.
La última línea del código hace una temperatura del objeto de propiedad. En pocas palabras, la propiedad adjunta algún código (get_temperature y set_temperature) a los atributos de los miembros (temperatura).
Cualquier código que recupere el valor de temperatura llamará automáticamente get_temperature () en lugar de una búsqueda de diccionario ( dict ). De forma similar, cualquier código que asigne un valor a la temperatura llamará automáticamente a set_temperature (). Esta es una característica genial en Python.
El método de propiedad debe contener getter y luego setter. No viceversa, Python llamará primer método (get_temperature) como getter y segundo método (set_temperature) como setter.
temperature = property(get_temperature,set_temperature)
Posible formador recursivo
En el método setter, si no usamos Underscore antes del atributo de objeto como a continuación, se produciría una llamada Recursive de setter y RecursionError.
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self.temperature = value
Sí, para el ejemplo original publicado, la propiedad funcionará exactamente igual que tener una variable de instancia ''x''.
Esto es lo mejor de las propiedades de Python. ¡Desde el exterior, funcionan exactamente como variables de instancia! Lo que le permite usar variables de instancia desde fuera de la clase.
Esto significa que su primer ejemplo podría usar una variable de instancia. Si las cosas cambiaron, y luego decide cambiar su implementación y una propiedad es útil, la interfaz de la propiedad seguirá siendo la misma desde el código fuera de la clase. Un cambio de variable de instancia a propiedad no tiene impacto en el código fuera de la clase.
Muchos otros lenguajes y cursos de programación instruirán que un programador nunca debe exponer variables de instancia, y en su lugar usar ''getters'' y ''setters'' para cualquier valor al que se pueda acceder desde fuera de la clase, incluso el caso simple como se cita en la pregunta.
Código fuera de la clase con muchos idiomas (por ejemplo, Java) uso
object.get_i()
#and
object.set_i(value)
#in place of (with python)
object.i
#and
object.i = value
Y al implementar la clase hay muchos ''getters'' y ''setters'' que hacen exactamente como su primer ejemplo: replicar una variable de instancia simple. Estos getters y setters son necesarios porque si la implementación de la clase cambia, todo el código fuera de la clase deberá cambiar. Pero las propiedades de Python permiten que el código fuera de la clase sea el mismo que con las variables de instancia. Por lo tanto, no es necesario cambiar el código fuera de la clase si agrega una propiedad o si tiene una variable de instancia simple. Así que a diferencia de la mayoría de los lenguajes orientados a objetos, para su ejemplo simple puede usar la variable de instancia en lugar de ''getters'' y ''setters'' que realmente no son necesarios, seguro sabiendo que si cambia a una propiedad en el futuro, el código usando tu clase no necesita cambiar
Esto significa que solo necesita crear propiedades si hay un comportamiento complejo, y para el caso simple muy común donde, como se describe en la pregunta, una simple variable de instancia es todo lo que se necesita, puede simplemente usar la variable de instancia.
Un caso de uso simple será establecer un atributo de instancia de solo lectura, ya que saber liderar un nombre de variable con un guión bajo _x
en python generalmente significa que es privado (uso interno) pero a veces queremos poder leer el atributo de instancia y no escríbalo para que podamos usar property
para esto:
>>> class C(object):
def __init__(self, x):
self._x = x
@property
def x(self):
return self._x
>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError Traceback (most recent call last)
AttributeError: can''t set attribute
Una cosa para la que lo he usado es el almacenamiento en caché de valores de búsqueda lenta, pero invariables, almacenados en una base de datos. Esto se generaliza a cualquier situación en la que sus atributos requieran computación u otra operación larga (por ejemplo, verificación de base de datos, comunicación de red) que solo quiera hacer bajo demanda.
class Model(object):
def get_a(self):
if not hasattr(self, "_a"):
self._a = self.db.lookup("a")
return self._a
a = property(get_a)
Esto era en una aplicación web donde cualquier vista de página dada solo podría necesitar un atributo particular de este tipo, pero los objetos subyacentes podrían tener varios de esos atributos, inicializarlos en la construcción sería un desperdicio, y las propiedades me permiten ser flexible en el cual los atributos son flojos y cuáles no.