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 @property
decorador 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é @property
es 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 temperature
atributo 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 temperature
como se muestra arriba, Python lo busca en el __dict__
atributo de diccionario integrado del objeto .
>>> human.__dict__ ('temperature': 37)
Por lo tanto, man.temperature
internamente 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, temperature
fue 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.temperature
to obj.get_temperature()
y todas las expresiones como obj.temperature = val
to 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 @property
viene al rescate.
La clase de propiedad
Una forma pitónica de lidiar con el problema anterior es usar la property
clase. 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_temperature
y 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 temperature
llamará automáticamente en get_temperature()
lugar de una búsqueda de diccionario (__dict__). De manera similar, cualquier código que asigne un valor a temperature
llamará 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.temperature
automá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 attributefset
is function to set value of the attributefdel
is function to delete the attributedoc
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_temperature
y set_temperature
ya que son innecesarios y contaminan el espacio de nombres de la clase.
Para esto, reutilizamos el temperature
nombre 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
.