Python @property: comment l'utiliser et pourquoi? - Programiz

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 @propertydécorateur intégré qui rend l'utilisation du getter et des setters beaucoup plus facile dans la programmation orientée objet.

Avant d'entrer dans les détails sur ce qu'est le @propertydécorateur, commençons par construire une intuition sur les raisons pour lesquelles il serait nécessaire en premier lieu.

Classe sans Getters et 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 l' temperatureattribut 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())

Production

 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 Erreur arithmétique à virgule flottante Python.

Chaque fois que nous attribuons ou récupérons un attribut d'objet comme temperatureindiqué ci-dessus, Python le recherche dans l' __dict__attribut de dictionnaire intégré de l'objet .

 >>> human.__dict__ ('temperature': 37)

Par conséquent, man.temperaturedevient intérieurement man.__dict__('temperature').

Utilisation de Getters et Setters

Supposons que nous souhaitons étendre la convivialité de la classe Celsius définie ci-dessus. Nous savons que la température de tout objet ne peut pas atteindre 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(le rendre privé) et de définir 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 nouvelles méthodes get_temperature()et set_temperature().

En outre, a temperatureété remplacé par _temperature. Un trait _de soulignement au début est utilisé pour désigner les 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())

Production

 37 98.60000000000001 Traceback (dernier appel en dernier): Fichier "", ligne 30, dans le fichier "", ligne 16, dans set_temperature ValueError: Une température inférieure à -273,15 n'est pas 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 à suivre. 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 de obj.temperatureà obj.get_temperature()et toutes les expressions comme obj.temperature = valà obj.set_temperature(val).

Ce refactoring peut poser des problèmes tout en traitant des centaines de milliers de lignes de codes.

Dans l'ensemble, notre nouvelle mise à jour n'était pas rétrocompatible. C'est là que @propertyvient le sauvetage.

La classe de propriété

Une manière pythonique de traiter le problème ci-dessus est d'utiliser la propertyclasse. 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é une print()fonction à l'intérieur get_temperature()et set_temperature()pour observer clairement qu'ils sont en cours d'exécution.

La dernière ligne du code crée un objet de propriété temperature. En termes simples, la propriété attache du code ( get_temperatureet set_temperature) à l'attribut membre accesses ( 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

Production

 Valeur de réglage… Obtenir la valeur… 37 Obtenir la valeur… 98.60000000000001 Valeur de réglage… Traceback (dernier appel le plus récent): Fichier "", ligne 31, dans le fichier "", ligne 18, dans set_temperature ValueError: Une température inférieure à -273 n'est pas possible

As we can see, any code that retrieves the value of temperature will automatically call get_temperature() instead of a dictionary (__dict__) look-up. Similarly, any code that assigns a value to temperature will automatically call set_temperature().

We can even see above that set_temperature() was called even when we created an object.

 >>> human = Celsius(37) Setting value… 

Can you guess why?

The reason is that when an object is created, the __init__() method gets called. This method has the line self.temperature = temperature. This expression automatically calls set_temperature().

Similarly, any access like c.temperature automatically calls get_temperature(). This is what property does. Here are a few more examples.

 >>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001

By using property, we can see that no modification is required in the implementation of the value constraint. Thus, our implementation is backward compatible.

Note: The actual temperature value is stored in the private _temperature variable. The temperature attribute is a property object which provides an interface to this private variable.

The @property Decorator

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is:

 property(fget=None, fset=None, fdel=None, doc=None)

where,

  • fget is function to get value of the attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc is a string (like a comment)

As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

 >>> property() 

A property object has three methods, getter(), setter(), and deleter() to specify fget, fset and fdel at a later point. This means, the line:

 temperature = property(get_temperature,set_temperature)

can be broken down as:

 # 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 familiers 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_temperatureet set_temperaturecomme ils sont inutiles et polluent l'espace de noms de classe.

Pour cela, nous réutilisons le temperaturenom 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)

Production

 Valeur de réglage… Obtenir la valeur… 37 Obtenir la valeur… 98.60000000000001 Valeur de réglage… Traceback (dernier appel le plus récent): Fichier "", ligne 29, dans le fichier "", ligne 4, dans __init__ Fichier "", ligne 18, dans la température ValueError: Une température inférieure à -273 n'est pas possible

La mise en œuvre ci-dessus est simple et efficace. C'est la manière recommandée d'utiliser property.

Articles intéressants...