Python @property decorador
Python @decorador de propiedades
En este tutorial, aprenderá sobre el decorador Python @property; una forma pitónica de usar getters y setters en la programación orientada a objetos.
La programación de Python nos proporciona un @property
incorporado decorador 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
decorador es, 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 a partir de esta clase y manipular el temperature
atributo como deseemos:
# 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 punto flotante. Para obtener más información, visite Error aritmético de punto flotante de Python.
Cada vez que asignamos o recuperamos cualquier atributo de objeto como temperature
como se muestra arriba, Python lo busca en el __dict__
integrado del objeto atributo de diccionario.
>>> human.__dict__
{'temperature': 37}
Por lo tanto, man.temperature
internamente se convierte en man.__dict__['temperature']
.
Uso de captadores y definidores
Supongamos que queremos extender la usabilidad del Celsius clase definida anteriormente. Sabemos que la temperatura de cualquier objeto no puede bajar por debajo de -273,15 grados centígrados (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
(hágalo privado) y defina nuevos métodos getter y setter para manipularlo. Esto se puede hacer 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 nuevos get_temperature()
y set_temperature()
métodos.
Además, temperature
fue reemplazado por _temperature
. Un guión bajo _
al principio se usa para indicar 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 Traceback (most recent call last): File "<string>", line 30, in <module> File "<string>", line 16, in set_temperature ValueError: Temperature below -273.15 is not possible.
Esta actualización implementó con éxito la nueva restricción. Ya no se nos permite establecer la temperatura por debajo de -273,15 grados centígrados.
Nota :Las variables privadas en realidad no existen 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
a obj.get_temperature()
y todas las expresiones como obj.temperature = val
a 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 a rescatar.
La clase de propiedad
Una forma pitónica de lidiar con el problema anterior es usar el 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 un print()
función dentro de get_temperature()
y set_temperature()
para observar claramente que están siendo ejecutados.
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
) a los accesos de atributo de miembro (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
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 31, in <module> File "<string>", line 18, in set_temperature ValueError: Temperature below -273 is not possible
Como podemos ver, cualquier código que recupera el valor de temperature
llamará automáticamente al get_temperature()
en lugar de una búsqueda de diccionario (__dict__). Del mismo modo, cualquier código que asigne un valor a temperature
llamará automáticamente al set_temperature()
.
Incluso podemos ver arriba que set_temperature()
fue llamado incluso cuando creamos un objeto.
>>> human = Celsius(37)
Setting value...
¿Puedes adivinar por qué?
La razón es que cuando se crea un objeto, el __init__()
se llama al método. Este método tiene la línea self.temperature = temperature
. Esta expresión llama automáticamente a set_temperature()
.
Del mismo modo, cualquier acceso como c.temperature
automáticamente llama a get_temperature()
. Esto es lo que hace la propiedad. Aquí hay algunos ejemplos más.
>>> human.temperature
Getting value
37
>>> human.temperature = 37
Setting value
>>> c.to_fahrenheit()
Getting value
98.60000000000001
Usando property
, podemos ver que no se requiere ninguna modificación en la implementación de la restricción de valor. Por lo tanto, nuestra implementación es compatible con versiones anteriores.
Nota :El valor de temperatura real se almacena en el privado _temperature
variable. El temperature
atributo es un objeto de propiedad que proporciona una interfaz a esta variable privada.
El @decorador de propiedades
En Python, property()
es una función integrada que crea y devuelve un property
objeto. La sintaxis de esta función es:
property(fget=None, fset=None, fdel=None, doc=None)
donde,
fget
es una función para obtener el valor del atributofset
es una función para establecer el valor del atributofdel
es función para eliminar el atributodoc
es una cadena (como un comentario)
Como se ve en la implementación, estos argumentos de función son opcionales. Entonces, un objeto de propiedad puede crearse simplemente de la siguiente manera.
>>> property()
<property object at 0x0000000003239B38>
Un objeto de propiedad tiene tres métodos, getter()
, setter()
y deleter()
para especificar fget
, fset
y fdel
en un punto posterior. Esto significa, la línea:
temperature = property(get_temperature,set_temperature)
se puede desglosar como:
# 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 Python Decorators 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 ello reutilizamos el temperature
name al definir 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
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "", line 29, in File " ", line 4, in __init__ File " ", line 18, in temperature ValueError: Temperature below -273 is not possible
La implementación anterior es simple y eficiente. Es la forma recomendada de usar property
.
python