Commençons par les décorateurs Python.
Un décorateur Python est une fonction qui permet d'ajouter des fonctionnalités supplémentaires à une fonction déjà définie.
En Python, tout est un objet. Les fonctions en Python sont des objets de première classe, ce qui signifie qu'elles peuvent être référencées par une variable, ajoutées dans les listes, passées comme arguments à une autre fonction, etc.
Considérez l'extrait de code suivant.
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
Ici, nous pouvons dire que la fonction décorateur a modifié notre fonction say_hello et y a ajouté quelques lignes de code supplémentaires.
Syntaxe Python pour décorateur
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()
Let's Conclu tout qu'avec un scénario de cas, mais avant cela, parlons de quelques principes Oups.
Les getters et setters sont utilisés dans de nombreux langages de programmation orientés objet pour garantir le principe de l'encapsulation des données (est considéré comme le regroupement des données avec les méthodes qui opèrent sur ces données.)
Ces méthodes sont bien sûr le getter pour récupérer les données et le setter pour changer les données.
Selon ce principe, les attributs d'une classe sont rendus privés pour les cacher et les protéger des autres codes.
Ouais , @property est fondamentalement une manière pythonique d'utiliser les getters et les setters.
Python a un grand concept appelé propriété qui rend la vie d'un programmeur orienté objet beaucoup plus simple.
Supposons que vous décidiez de créer une classe capable de stocker la température en degrés 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
Code refactorisé, voici comment nous aurions pu le réaliser avec propriété.
En Python, property () est une fonction intégrée qui crée et renvoie un objet de propriété.
Un objet de propriété a trois méthodes, getter (), setter () et 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)
Ici,
temperature = property(get_temperature,set_temperature)
aurait pu être décomposé car,
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Point à noter:
- get_temperature reste une propriété au lieu d'une méthode.
Vous pouvez maintenant accéder à la valeur de la température en écrivant.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Nous pouvons continuer et ne pas définir les noms get_temperature et set_temperature car ils sont inutiles et polluent l'espace de noms de classe.
La manière pythonique de traiter le problème ci-dessus consiste à utiliser @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
Points à noter -
- Une méthode utilisée pour obtenir une valeur est décorée avec "@property".
- La méthode qui doit fonctionner comme le passeur est décorée avec "@ temperature.setter", Si la fonction avait été appelée "x", il faudrait la décorer avec "@ x.setter".
- Nous avons écrit "deux" méthodes avec le même nom et un nombre différent de paramètres "def temperature (self)" et "def temperature (self, x)".
Comme vous pouvez le voir, le code est nettement moins élégant.
Maintenant, parlons d'un scénario pratique réel.
Disons que vous avez conçu une classe comme suit:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
Supposons maintenant que notre classe est devenue populaire parmi les clients et qu'ils ont commencé à l'utiliser dans leurs programmes. Ils ont fait toutes sortes d'affectations à l'objet.
Et un jour fatidique, un client de confiance est venu vers nous et a suggéré que "x" doit être une valeur entre 0 et 1000, c'est vraiment un scénario horrible!
En raison des propriétés, c'est facile: nous créons une version de propriété 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
C'est super, n'est-ce pas: vous pouvez commencer avec l'implémentation la plus simple imaginable, et vous êtes libre de migrer plus tard vers une version de propriété sans avoir à changer l'interface! Les propriétés ne remplacent donc pas seulement les getters et les setter!
Vous pouvez vérifier cette implémentation ici