Décorateur Python @property
Python @décorateur de propriétés
Dans ce didacticiel, vous découvrirez le décorateur Python @property ; une manière pythonique d'utiliser les getters et les setters dans la programmation orientée objet.
La programmation Python nous fournit un @property
intégré décorateur qui rend l'utilisation de getter et de setters beaucoup plus facile dans la programmation orientée objet.
Avant d'entrer dans les détails de ce que @property
décorateur est, construisons d'abord une intuition sur la raison pour laquelle il serait nécessaire en premier lieu.
Classe sans getters ni setters
Supposons que nous décidions de créer une classe qui stocke la température en degrés Celsius. Il mettrait également en œuvre une méthode pour convertir la température en degrés Fahrenheit. Une façon de procéder est la suivante :
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
Nous pouvons créer des objets à partir de cette classe et manipuler le temperature
attribut comme nous le souhaitons :
# 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())
Sortie
37 98.60000000000001
Les décimales supplémentaires lors de la conversion en Fahrenheit sont dues à l'erreur arithmétique en virgule flottante. Pour en savoir plus, consultez Python Floating Point Arithmetic Error.
Chaque fois que nous attribuons ou récupérons un attribut d'objet comme temperature
comme indiqué ci-dessus, Python le recherche dans le __dict__
intégré de l'objet attribut de dictionnaire.
>>> human.__dict__
{'temperature': 37}
Par conséquent, man.temperature
en interne devient man.__dict__['temperature']
.
Utiliser des getters et des setters
Supposons que nous voulions étendre la convivialité du Celsius classe définie ci-dessus. Nous savons que la température de tout objet ne peut pas descendre en dessous de -273,15 degrés Celsius (zéro absolu en thermodynamique)
Mettons à jour notre code pour implémenter cette contrainte de valeur.
Une solution évidente à la restriction ci-dessus sera de masquer l'attribut temperature
(rendez-le privé) et définissez de nouvelles méthodes getter et setter pour le manipuler. Cela peut être fait comme suit :
# 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
Comme nous pouvons le voir, la méthode ci-dessus introduit deux nouveaux get_temperature()
et set_temperature()
méthodes.
De plus, temperature
a été remplacé par _temperature
. Un trait de soulignement _
au début est utilisé pour désigner des variables privées en Python.
Maintenant, utilisons cette implémentation :
# 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())
Sortie
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.
Cette mise à jour a implémenté avec succès la nouvelle restriction. Nous ne sommes plus autorisés à régler la température en dessous de -273,15 degrés Celsius.
Remarque :Les variables privées n'existent pas réellement en Python. Il y a simplement des normes à respecter. La langue elle-même n'applique aucune restriction.
>>> human._temperature = -300
>>> human.get_temperature()
-300
Cependant, le plus gros problème avec la mise à jour ci-dessus est que tous les programmes qui ont implémenté notre classe précédente doivent modifier leur code à partir de obj.temperature
à obj.get_temperature()
et toutes les expressions comme obj.temperature = val
à obj.set_temperature(val)
.
Cette refactorisation peut causer des problèmes lors du traitement de centaines de milliers de lignes de code.
Dans l'ensemble, notre nouvelle mise à jour n'était pas rétrocompatible. C'est là que @property
vient à la rescousse.
La classe de propriété
Une façon pythonique de traiter le problème ci-dessus consiste à utiliser le property
classer. Voici comment nous pouvons mettre à jour notre code :
# 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)
Nous avons ajouté un print()
fonction à l'intérieur de get_temperature()
et set_temperature()
pour observer clairement qu'ils sont exécutés.
La dernière ligne du code fait un objet de propriété temperature
. En termes simples, la propriété attache du code (get_temperature
et set_temperature
) aux accès aux attributs de membre (temperature
).
Utilisons ce code de mise à jour :
# 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
Sortie
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
Comme nous pouvons le voir, tout code qui récupère la valeur de temperature
appellera automatiquement le get_temperature()
au lieu d'une recherche dans un dictionnaire (__dict__). De même, tout code qui attribue une valeur à temperature
appellera automatiquement le set_temperature()
.
On peut même voir plus haut que set_temperature()
a été appelé même lorsque nous avons créé un objet.
>>> human = Celsius(37)
Setting value...
Pouvez-vous deviner pourquoi ?
La raison est que lorsqu'un objet est créé, le __init__()
méthode est appelée. Cette méthode a la ligne self.temperature = temperature
. Cette expression appelle automatiquement set_temperature()
.
De même, tout accès comme c.temperature
appelle automatiquement le get_temperature()
. C'est ce que fait la propriété. Voici quelques exemples supplémentaires.
>>> human.temperature
Getting value
37
>>> human.temperature = 37
Setting value
>>> c.to_fahrenheit()
Getting value
98.60000000000001
En utilisant property
, nous pouvons voir qu'aucune modification n'est nécessaire dans l'implémentation de la contrainte de valeur. Ainsi, notre implémentation est rétrocompatible.
Remarque :La valeur de température réelle est stockée dans le _temperature
privé variable. Le temperature
L'attribut est un objet de propriété qui fournit une interface à cette variable privée.
Le décorateur @property
En Python, property()
est une fonction intégrée qui crée et renvoie un property
objet. La syntaxe de cette fonction est :
property(fget=None, fset=None, fdel=None, doc=None)
où,
fget
est une fonction pour obtenir la valeur de l'attributfset
est une fonction pour définir la valeur de l'attributfdel
est une fonction pour supprimer l'attributdoc
est une chaîne (comme un commentaire)
Comme le montre l'implémentation, ces arguments de fonction sont facultatifs. Ainsi, un objet de propriété peut simplement être créé comme suit.
>>> property()
<property object at 0x0000000003239B38>
Un objet de propriété a trois méthodes, getter()
, setter()
, et deleter()
pour spécifier fget
, fset
et fdel
à un moment ultérieur. Cela signifie, la ligne :
temperature = property(get_temperature,set_temperature)
peut être décomposé en :
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Ces deux morceaux de codes sont équivalents.
Les programmeurs familiarisés avec les décorateurs Python peuvent reconnaître que la construction ci-dessus peut être implémentée en tant que décorateurs.
On ne peut même pas définir les noms get_temperature
et set_temperature
car ils sont inutiles et polluent l'espace de noms de classe.
Pour cela, nous réutilisons le temperature
name tout en définissant nos fonctions getter et setter. Voyons comment implémenter cela en tant que décorateur :
# 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)
Sortie
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
L'implémentation ci-dessus est simple et efficace. C'est la manière recommandée d'utiliser property
.
Python