Python @property: ¿Cómo se usa y por qué? - Programiz

En este tutorial, aprenderá sobre el decorador @property de Python; una forma pitónica de usar getters y setters en programación orientada a objetos.

La programación de Python nos proporciona un @propertydecorador integrado que hace que el uso de getter y setters sea mucho más fácil en la programación orientada a objetos.

Antes de entrar en detalles sobre qué @propertyes el decorador, primero construyamos una intuición sobre por qué sería necesario en primer lugar.

Clase sin getters y setters

Supongamos que decidimos hacer una clase que almacene 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

Podemos hacer objetos de esta clase y manipular el temperatureatributo como queramos:

 # Basic method of setting and getting attributes in Python class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # Create a new object human = Celsius() # Set the temperature human.temperature = 37 # Get the temperature attribute print(human.temperature) # Get the to_fahrenheit method print(human.to_fahrenheit())

Salida

 37 98.60000000000001

Los lugares decimales adicionales al convertir a Fahrenheit se deben al error aritmético de coma flotante. Para obtener más información, visite Error aritmético de punto flotante de Python.

Siempre que asignamos o recuperamos cualquier atributo de objeto temperaturecomo se muestra arriba, Python lo busca en el __dict__atributo de diccionario integrado del objeto .

 >>> human.__dict__ ('temperature': 37)

Por lo tanto, man.temperatureinternamente se vuelve man.__dict__('temperature').

Usando Getters y Setters

Supongamos que queremos ampliar la usabilidad de la clase Celsius definida anteriormente. Sabemos que la temperatura de cualquier objeto no puede llegar por debajo de los -273,15 grados Celsius (cero absoluto en termodinámica)

Actualicemos nuestro código para implementar esta restricción de valor.

Una solución obvia a la restricción anterior será ocultar el atributo temperature(hacerlo privado) y definir nuevos métodos getter y setter para manipularlo. Esto puede hacerse de la siguiente manera:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value

Como podemos ver, el método anterior introduce dos métodos nuevos get_temperature()y set_temperature().

Además, temperaturefue reemplazado por _temperature. Se _usa un guión bajo al principio para denotar variables privadas en Python.

Ahora, usemos esta implementación:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value # Create a new object, set_temperature() internally called by __init__ human = Celsius(37) # Get the temperature attribute via a getter print(human.get_temperature()) # Get the to_fahrenheit method, get_temperature() called by the method itself print(human.to_fahrenheit()) # new constraint implementation human.set_temperature(-300) # Get the to_fahreheit method print(human.to_fahrenheit())

Salida

 37 98.60000000000001 Rastreo (última llamada más reciente): Archivo "", línea 30, en Archivo "", línea 16, en set_temperature ValueError: No es posible una temperatura por debajo de -273,15.

Esta actualización implementó con éxito la nueva restricción. Ya no podemos ajustar la temperatura por debajo de -273,15 grados Celsius.

Nota : Las variables privadas no existen realmente en Python. Simplemente hay normas a seguir. El idioma en sí no aplica ninguna restricción.

 >>> human._temperature = -300 >>> human.get_temperature() -300

Sin embargo, el mayor problema con la actualización anterior es que todos los programas que implementaron nuestra clase anterior tienen que modificar su código de obj.temperatureto obj.get_temperature()y todas las expresiones como obj.temperature = valto obj.set_temperature(val).

Esta refactorización puede causar problemas al tratar con cientos de miles de líneas de códigos.

Con todo, nuestra nueva actualización no era compatible con versiones anteriores. Aquí es donde @propertyviene al rescate.

La clase de propiedad

Una forma pitónica de lidiar con el problema anterior es usar la propertyclase. Así es como podemos actualizar nuestro código:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature)

Agregamos una print()función dentro get_temperature()y set_temperature()para observar claramente que se están ejecutando.

La última línea del código crea un objeto de propiedad temperature. En pocas palabras, la propiedad adjunta algún código ( get_temperaturey set_temperature) al atributo de miembro accesses ( temperature).

Usemos este código de actualización:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature) human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) human.temperature = -300

Salida

 Configurando valor … Obteniendo valor … 37 Obteniendo valor … 98.60000000000001 Configurando valor … Traceback (última llamada más reciente): Archivo "", línea 31, en Archivo "", línea 18, en set_temperature ValueError: Temperatura por debajo de -273 no es posible

Como podemos ver, cualquier código que recupere el valor de temperaturellamará automáticamente en get_temperature()lugar de una búsqueda de diccionario (__dict__). De manera similar, cualquier código que asigne un valor a temperaturellamará automáticamente set_temperature().

Incluso podemos ver arriba que set_temperature()se llamó incluso cuando creamos un objeto.

 >>> human = Celsius(37) Setting value… 

¿Puedes adivinar por qué?

La razón es que cuando se crea un objeto, __init__()se llama al método. Este método tiene la línea self.temperature = temperature. Esta expresión llama automáticamente set_temperature().

Del mismo modo, cualquier acceso como c.temperatureautomáticamente llama get_temperature(). Esto es lo que hace la propiedad. Aqui hay algunos ejemplos mas.

 >>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001

Al usar property, podemos ver que no se requiere modificación en la implementación de la restricción de valor. Por lo tanto, nuestra implementación es compatible con versiones anteriores.

Note: The actual temperature value is stored in the private _temperature variable. The temperature attribute is a property object which provides an interface to this private variable.

The @property Decorator

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is:

 property(fget=None, fset=None, fdel=None, doc=None)

where,

  • fget is function to get value of the attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc is a string (like a comment)

As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

 >>> property() 

A property object has three methods, getter(), setter(), and deleter() to specify fget, fset and fdel at a later point. This means, the line:

 temperature = property(get_temperature,set_temperature)

can be broken down as:

 # make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)

Estos dos códigos son equivalentes.

Los programadores familiarizados con los decoradores de Python pueden reconocer que la construcción anterior se puede implementar como decoradores.

Incluso no podemos definir los nombres get_temperaturey set_temperatureya que son innecesarios y contaminan el espacio de nombres de la clase.

Para esto, reutilizamos el temperaturenombre mientras definimos nuestras funciones getter y setter. Veamos cómo implementar esto como decorador:

 # Using @property decorator class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("Getting value… ") return self._temperature @temperature.setter def temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value # create an object human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) coldest_thing = Celsius(-300)

Salida

 Valor de configuración … Obteniendo valor … 37 Obteniendo valor … 98.60000000000001 Valor de configuración … Traceback (última llamada más reciente): Archivo "", línea 29, en Archivo "", línea 4, en __init__ Archivo "", línea 18, en temperatura ValueError: No es posible una temperatura por debajo de -273

La implementación anterior es simple y eficiente. Es la forma recomendada de uso property.

Articulos interesantes...