Cominciamo con i decoratori di Python.
Un decoratore Python è una funzione che aiuta ad aggiungere alcune funzionalità aggiuntive a una funzione già definita.
In Python, tutto è un oggetto. Le funzioni in Python sono oggetti di prima classe, il che significa che possono essere referenziati da una variabile, aggiunti negli elenchi, passati come argomenti a un'altra funzione ecc.
Considera il seguente frammento di codice.
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
Qui, possiamo dire che la funzione decoratore ha modificato la nostra funzione say_hello e ha aggiunto alcune righe di codice al suo interno.
Sintassi di Python per decoratore
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()
Concludiamo tutto che con uno scenario di caso, ma prima parliamo di alcuni principi di oops.
Getter e setter sono utilizzati in molti linguaggi di programmazione orientati agli oggetti per garantire il principio dell'incapsulamento dei dati (è visto come il raggruppamento di dati con i metodi che operano su questi dati).
Questi metodi sono ovviamente il getter per il recupero dei dati e il setter per la modifica dei dati.
Secondo questo principio, gli attributi di una classe vengono resi privati per nasconderli e proteggerli da altri codici.
Sì , @property è fondamentalmente un modo pitonico per usare getter e setter.
Python ha un grande concetto chiamato proprietà che rende la vita di un programmatore orientato agli oggetti molto più semplice.
Supponiamo che tu decida di creare una classe in grado di memorizzare la temperatura in gradi 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
Codice refactored, ecco come avremmo potuto raggiungerlo con la proprietà.
In Python, property () è una funzione integrata che crea e restituisce un oggetto proprietà.
Un oggetto proprietà ha tre metodi, getter (), setter () e 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)
Qui,
temperature = property(get_temperature,set_temperature)
avrebbe potuto essere scomposto come,
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Nota:
- get_temperature rimane una proprietà anziché un metodo.
Ora puoi accedere al valore della temperatura scrivendo.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Possiamo continuare ulteriormente e non definire i nomi get_temperature e set_temperature poiché non sono necessari e inquinano lo spazio dei nomi della classe.
Il modo pitonico per affrontare il problema sopra è usare @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
Punti da notare -
- Un metodo utilizzato per ottenere un valore è decorato con "@property".
- Il metodo che deve funzionare come setter è decorato con "@ temperature.setter". Se la funzione fosse stata chiamata "x", dovremmo decorarla con "@ x.setter".
- Abbiamo scritto "due" metodi con lo stesso nome e un diverso numero di parametri "def temperature (self)" e "def temperature (self, x)".
Come puoi vedere, il codice è decisamente meno elegante.
Ora parliamo di uno scenario pratico nella vita reale.
Supponiamo che tu abbia progettato una classe come segue:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
Ora, supponiamo inoltre che la nostra classe sia diventata popolare tra i clienti e che abbiano iniziato a usarlo nei loro programmi. Hanno fatto tutti i tipi di compiti sull'oggetto.
E un giorno fatidico, un cliente fidato è venuto da noi e ha suggerito che "x" deve essere un valore compreso tra 0 e 1000, questo è davvero uno scenario orribile!
Per via delle proprietà è semplice: creiamo una versione di proprietà di "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
È fantastico, non è vero: puoi iniziare con l'implementazione più semplice immaginabile e sei libero di migrare in seguito a una versione di proprietà senza dover cambiare l'interfaccia! Quindi le proprietà non sono solo una sostituzione per getter e setter!
Puoi controllare questa implementazione qui