Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial programming >> Python

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'attribut
  • fset est une fonction pour définir la valeur de l'attribut
  • fdel est une fonction pour supprimer l'attribut
  • doc 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

  1. Types de données Python
  2. Opérateurs Python
  3. Instruction de passe Python
  4. Arguments de la fonction Python
  5. Dictionnaire Python
  6. Héritage Python
  7. Surcharge d'opérateur Python
  8. Itérateurs Python
  9. Fermetures Python