Comencemos con los decoradores de Python.
Un decorador de Python es una función que ayuda a agregar algunas funcionalidades adicionales a una función ya definida.
En Python, todo es un objeto. Las funciones en Python son objetos de primera clase, lo que significa que pueden ser referenciadas por una variable, agregadas en las listas, pasadas como argumentos a otra función, etc.
Considere el siguiente fragmento de código.
def decorator_func(fun):
def wrapper_func():
print("Wrapper function started")
fun()
print("Given function decorated")
# Wrapper function add something to the passed function and decorator
# returns the wrapper function
return wrapper_func
def say_bye():
print("bye!!")
say_bye = decorator_func(say_bye)
say_bye()
# Output:
# Wrapper function started
# bye
# Given function decorated
Aquí, podemos decir que la función decoradora modificó nuestra función say_hello y agregó algunas líneas de código adicionales.
Sintaxis de Python para decorador
def decorator_func(fun):
def wrapper_func():
print("Wrapper function started")
fun()
print("Given function decorated")
# Wrapper function add something to the passed function and decorator
# returns the wrapper function
return wrapper_func
@decorator_func
def say_bye():
print("bye!!")
say_bye()
Concluimos todo más que con un escenario de caso, pero antes de eso hablemos sobre algunos principios de Uy.
Los getters y setters se utilizan en muchos lenguajes de programación orientados a objetos para garantizar el principio de la encapsulación de datos (se ve como la agrupación de datos con los métodos que operan en estos datos).
Estos métodos son, por supuesto, el getter para recuperar los datos y el setter para cambiar los datos.
De acuerdo con este principio, los atributos de una clase se hacen privados para ocultarlos y protegerlos de otro código.
Sí, @property es básicamente una forma pitónica de usar getters y setters.
Python tiene un gran concepto llamado propiedad que hace que la vida de un programador orientado a objetos sea mucho más simple.
Supongamos que decide hacer una clase que pueda almacenar la temperatura en grados Celsius.
class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
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
Código refactorizado, así es como podríamos haberlo logrado con la propiedad
En Python, property () es una función integrada que crea y devuelve un objeto de propiedad.
Un objeto de propiedad tiene tres métodos, getter (), setter () y delete ().
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)
Aquí,
temperature = property(get_temperature,set_temperature)
podría haberse desglosado como,
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Punto a tener en cuenta:
- get_temperature sigue siendo una propiedad en lugar de un método.
Ahora puede acceder al valor de la temperatura escribiendo.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Podemos continuar y no definir los nombres get_temperature y set_temperature ya que son innecesarios y contaminan el espacio de nombres de la clase.
La forma pitónica de tratar el problema anterior es utilizar @property .
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):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self.temperature = value
Puntos a tener en cuenta:
- Un método que se utiliza para obtener un valor está decorado con "@property".
- El método que tiene que funcionar como setter está decorado con "@ temperature.setter". Si la función se hubiera llamado "x", tendríamos que decorarla con "@ x.setter".
- Escribimos "dos" métodos con el mismo nombre y un número diferente de parámetros "def temperature (self)" y "def temperature (self, x)".
Como puede ver, el código es definitivamente menos elegante.
Ahora, hablemos de un escenario práctico de la vida real.
Digamos que ha diseñado una clase de la siguiente manera:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
Ahora, supongamos que nuestra clase se hizo popular entre los clientes y comenzaron a usarla en sus programas. Hicieron todo tipo de tareas para el objeto.
Y un día fatídico, un cliente de confianza vino a nosotros y sugirió que "x" tiene que ser un valor entre 0 y 1000, ¡este es realmente un escenario horrible!
Debido a las propiedades es fácil: creamos una versión de propiedad de "x".
class OurClass:
def __init__(self,x):
self.x = x
@property
def x(self):
return self.__x
@x.setter
def x(self, x):
if x < 0:
self.__x = 0
elif x > 1000:
self.__x = 1000
else:
self.__x = x
Esto es genial, ¿no es así: puede comenzar con la implementación más simple imaginable, y luego puede migrar a una versión de propiedad sin tener que cambiar la interfaz! ¡Entonces las propiedades no son solo un reemplazo para captadores y colocadores!
Puedes consultar esta Implementación aquí