ylab color change python oop properties python-decorators

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.