Attributs Privés et Propriétés

PythonPythonIntermediate
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Dans ce laboratoire, vous apprendrez à encapsuler les éléments internes d'un objet en utilisant des attributs privés et à implémenter des décorateurs de propriété (property decorators) pour contrôler l'accès aux attributs. Ces techniques sont essentielles pour maintenir l'intégrité de vos objets et garantir une gestion appropriée des données.

Vous comprendrez également comment restreindre la création d'attributs en utilisant __slots__. Nous modifierons le fichier stock.py tout au long de ce laboratoire pour appliquer ces concepts.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("Encapsulation") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("Raising Exceptions") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/conditional_statements -.-> lab-132494{{"Attributs Privés et Propriétés"}} python/classes_objects -.-> lab-132494{{"Attributs Privés et Propriétés"}} python/encapsulation -.-> lab-132494{{"Attributs Privés et Propriétés"}} python/raising_exceptions -.-> lab-132494{{"Attributs Privés et Propriétés"}} python/decorators -.-> lab-132494{{"Attributs Privés et Propriétés"}} end

Implémentation d'attributs privés

En Python, nous utilisons une convention de nommage pour indiquer qu'un attribut est destiné à un usage interne au sein d'une classe. Nous préfixons ces attributs avec un underscore (_). Cela signale aux autres développeurs que ces attributs ne font pas partie de l'API publique et ne doivent pas être accessibles directement depuis l'extérieur de la classe.

Examinons la classe Stock actuelle dans le fichier stock.py. Elle possède une variable de classe nommée types.

class Stock:
    ## Class variable for type conversions
    types = (str, int, float)

    ## Rest of the class...

La variable de classe types est utilisée en interne pour convertir les données des lignes. Pour indiquer qu'il s'agit d'un détail d'implémentation, nous allons la marquer comme privée.

Instructions :

  1. Ouvrez le fichier stock.py dans l'éditeur.

  2. Modifiez la variable de classe types en ajoutant un underscore au début, en la remplaçant par _types.

    class Stock:
        ## Class variable for type conversions
        _types = (str, int, float)
    
        ## Rest of the class...
  3. Mettez à jour la méthode from_row pour utiliser la variable renommée _types.

    @classmethod
    def from_row(cls, row):
        values = [func(val) for func, val in zip(cls._types, row)]
        return cls(*values)
  4. Enregistrez le fichier stock.py.

  5. Créez un script Python nommé test_stock.py pour tester vos modifications. Vous pouvez créer le fichier dans l'éditeur en utilisant la commande suivante :

    touch /home/labex/project/test_stock.py
  6. Ajoutez le code suivant au fichier test_stock.py. Ce code crée des instances de la classe Stock et affiche des informations à leur sujet.

    from stock import Stock
    
    ## Create a stock instance
    s = Stock('GOOG', 100, 490.10)
    print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}")
    print(f"Cost: {s.cost()}")
    
    ## Create from row
    row = ['AAPL', '50', '142.5']
    apple = Stock.from_row(row)
    print(f"Name: {apple.name}, Shares: {apple.shares}, Price: {apple.price}")
    print(f"Cost: {apple.cost()}")
  7. Exécutez le script de test en utilisant la commande suivante dans le terminal :

    python /home/labex/project/test_stock.py

    Vous devriez voir une sortie similaire à :

    Name: GOOG, Shares: 100, Price: 490.1
    Cost: 49010.0
    Name: AAPL, Shares: 50, Price: 142.5
    Cost: 7125.0

Conversion de méthodes en propriétés

Les propriétés (properties) en Python vous permettent d'accéder à des valeurs calculées comme des attributs. Cela élimine le besoin de parenthèses lors de l'appel d'une méthode, ce qui rend votre code plus propre et plus cohérent.

Actuellement, notre classe Stock possède une méthode cost() qui calcule le coût total des actions.

def cost(self):
    return self.shares * self.price

Pour obtenir la valeur du coût, nous devons l'appeler avec des parenthèses :

s = Stock('GOOG', 100, 490.10)
print(s.cost())  ## Calls the method

Nous pouvons améliorer cela en convertissant la méthode cost() en une propriété, ce qui nous permet d'accéder à la valeur du coût sans parenthèses :

s = Stock('GOOG', 100, 490.10)
print(s.cost)  ## Accesses the property

Instructions :

  1. Ouvrez le fichier stock.py dans l'éditeur.

  2. Remplacez la méthode cost() par une propriété en utilisant le décorateur @property :

    @property
    def cost(self):
        return self.shares * self.price
  3. Enregistrez le fichier stock.py.

  4. Créez un nouveau fichier nommé test_property.py dans l'éditeur :

    touch /home/labex/project/test_property.py
  5. Ajoutez le code suivant au fichier test_property.py pour créer une instance Stock et accéder à la propriété cost :

    from stock import Stock
    
    ## Create a stock instance
    s = Stock('GOOG', 100, 490.10)
    
    ## Access cost as a property (no parentheses)
    print(f"Stock: {s.name}")
    print(f"Shares: {s.shares}")
    print(f"Price: {s.price}")
    print(f"Cost: {s.cost}")  ## Using the property
  6. Exécutez le script de test :

    python /home/labex/project/test_property.py

    Vous devriez voir une sortie similaire à :

    Stock: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
✨ Vérifier la solution et pratiquer

Implémentation de la validation des propriétés

Les propriétés vous permettent également de contrôler la manière dont les valeurs des attributs sont récupérées, définies et supprimées. Ceci est utile pour ajouter une validation à vos attributs, en vous assurant que les valeurs répondent à des critères spécifiques.

Dans notre classe Stock, nous voulons nous assurer que shares est un entier non négatif et que price est un flottant non négatif. Nous utiliserons des décorateurs de propriété (property decorators) avec des getters et des setters pour y parvenir.

Instructions :

  1. Ouvrez le fichier stock.py dans l'éditeur.

  2. Ajoutez les attributs privés _shares et _price à la classe Stock et modifiez le constructeur pour les utiliser :

    def __init__(self, name, shares, price):
        self.name = name
        self._shares = shares  ## Using private attribute
        self._price = price    ## Using private attribute
  3. Définissez des propriétés pour shares et price avec validation :

    @property
    def shares(self):
        return self._shares
    
    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError("Expected integer")
        if value < 0:
            raise ValueError("shares must be >= 0")
        self._shares = value
    
    @property
    def price(self):
        return self._price
    
    @price.setter
    def price(self, value):
        if not isinstance(value, float):
            raise TypeError("Expected float")
        if value < 0:
            raise ValueError("price must be >= 0")
        self._price = value
  4. Mettez à jour le constructeur pour utiliser les setters de propriété pour la validation :

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares  ## Using property setter
        self.price = price    ## Using property setter
  5. Enregistrez le fichier stock.py.

  6. Créez un script de test nommé test_validation.py :

    touch /home/labex/project/test_validation.py
  7. Ajoutez le code suivant au fichier test_validation.py :

    from stock import Stock
    
    ## Create a valid stock instance
    s = Stock('GOOG', 100, 490.10)
    print(f"Initial: Name={s.name}, Shares={s.shares}, Price={s.price}, Cost={s.cost}")
    
    ## Test valid updates
    try:
        s.shares = 50  ## Valid update
        print(f"After setting shares=50: Shares={s.shares}, Cost={s.cost}")
    except Exception as e:
        print(f"Error setting shares=50: {e}")
    
    try:
        s.price = 123.45  ## Valid update
        print(f"After setting price=123.45: Price={s.price}, Cost={s.cost}")
    except Exception as e:
        print(f"Error setting price=123.45: {e}")
    
    ## Test invalid updates
    try:
        s.shares = "50"  ## Invalid type (string)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting shares='50': {e}")
    
    try:
        s.shares = -10  ## Invalid value (negative)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting shares=-10: {e}")
    
    try:
        s.price = "123.45"  ## Invalid type (string)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting price='123.45': {e}")
    
    try:
        s.price = -10.0  ## Invalid value (negative)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting price=-10.0: {e}")
  8. Exécutez le script de test :

    python /home/labex/project/test_validation.py

    Vous devriez voir une sortie montrant les mises à jour valides réussies et les messages d'erreur appropriés pour les mises à jour non valides.

    Initial: Name=GOOG, Shares=100, Price=490.1, Cost=49010.0
    After setting shares=50: Shares=50, Cost=24505.0
    After setting price=123.45: Price=123.45, Cost=6172.5
    Error setting shares='50': Expected integer
    Error setting shares=-10: shares must be >= 0
    Error setting price='123.45': Expected float
    Error setting price=-10.0: price must be >= 0
✨ Vérifier la solution et pratiquer

Utilisation de __slots__ pour l'optimisation de la mémoire

L'attribut __slots__ restreint les attributs qu'une classe peut avoir. Il empêche l'ajout de nouveaux attributs aux instances et réduit l'utilisation de la mémoire.

Dans notre classe Stock, nous utiliserons __slots__ pour :

  1. Restreindre la création d'attributs aux seuls attributs que nous avons définis.
  2. Améliorer l'efficacité de la mémoire, en particulier lors de la création de nombreuses instances.

Instructions :

  1. Ouvrez le fichier stock.py dans l'éditeur.

  2. Ajoutez une variable de classe __slots__, en listant tous les noms d'attributs privés utilisés par la classe :

    class Stock:
        ## Class variable for type conversions
        _types = (str, int, float)
    
        ## Define slots to restrict attribute creation
        __slots__ = ('name', '_shares', '_price')
    
        ## Rest of the class...
  3. Enregistrez le fichier.

  4. Créez un script de test nommé test_slots.py :

    touch /home/labex/project/test_slots.py
  5. Ajoutez le code suivant au fichier test_slots.py :

    from stock import Stock
    
    ## Create a stock instance
    s = Stock('GOOG', 100, 490.10)
    
    ## Access existing attributes
    print(f"Name: {s.name}")
    print(f"Shares: {s.shares}")
    print(f"Price: {s.price}")
    print(f"Cost: {s.cost}")
    
    ## Try to add a new attribute
    try:
        s.extra = "This will fail"
        print(f"Extra: {s.extra}")
    except AttributeError as e:
        print(f"Error: {e}")
  6. Exécutez le script de test :

    python /home/labex/project/test_slots.py

    Vous devriez voir une sortie montrant que vous pouvez accéder aux attributs définis, mais que tenter d'ajouter un nouvel attribut lève une AttributeError.

    Name: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
    Error: 'Stock' object has no attribute 'extra'
✨ Vérifier la solution et pratiquer

Conciliation de la validation de type avec les variables de classe

Actuellement, notre classe Stock utilise à la fois la variable de classe _types et les setters de propriété (property setters) pour la gestion des types. Pour améliorer la cohérence et la maintenabilité, nous allons concilier ces mécanismes afin qu'ils utilisent les mêmes informations de type.

Instructions :

  1. Ouvrez le fichier stock.py dans l'éditeur.

  2. Modifiez les setters de propriété pour utiliser les types définis dans la variable de classe _types :

    @property
    def shares(self):
        return self._shares
    
    @shares.setter
    def shares(self, value):
        if not isinstance(value, self._types[1]):
            raise TypeError(f"Expected {self._types[1].__name__}")
        if value < 0:
            raise ValueError("shares must be >= 0")
        self._shares = value
    
    @property
    def price(self):
        return self._price
    
    @price.setter
    def price(self, value):
        if not isinstance(value, self._types[2]):
            raise TypeError(f"Expected {self._types[2].__name__}")
        if value < 0:
            raise ValueError("price must be >= 0")
        self._price = value
  3. Enregistrez le fichier stock.py.

  4. Créez un script de test nommé test_subclass.py :

    touch /home/labex/project/test_subclass.py
  5. Ajoutez le code suivant au fichier test_subclass.py :

    from stock import Stock
    from decimal import Decimal
    
    ## Create a subclass with different types
    class DStock(Stock):
        _types = (str, int, Decimal)
    
    ## Test the base class
    s = Stock('GOOG', 100, 490.10)
    print(f"Stock: {s.name}, Shares: {s.shares}, Price: {s.price}, Cost: {s.cost}")
    
    ## Test valid update with float
    try:
        s.price = 500.25
        print(f"Updated Stock price: {s.price}, Cost: {s.cost}")
    except Exception as e:
        print(f"Error updating Stock price: {e}")
    
    ## Test the subclass with Decimal
    ds = DStock('AAPL', 50, Decimal('142.50'))
    print(f"DStock: {ds.name}, Shares: {ds.shares}, Price: {ds.price}, Cost: {ds.cost}")
    
    ## Test invalid update with float (should require Decimal)
    try:
        ds.price = 150.75
        print(f"Updated DStock price: {ds.price}")
    except Exception as e:
        print(f"Error updating DStock price: {e}")
    
    ## Test valid update with Decimal
    try:
        ds.price = Decimal('155.25')
        print(f"Updated DStock price: {ds.price}, Cost: {ds.cost}")
    except Exception as e:
        print(f"Error updating DStock price: {e}")
  6. Exécutez le script de test :

    python /home/labex/project/test_subclass.py

    Vous devriez voir que la classe de base Stock accepte les valeurs float pour le prix, tandis que la sous-classe DStock exige des valeurs Decimal.

    Stock: GOOG, Shares: 100, Price: 490.1, Cost: 49010.0
    Updated Stock price: 500.25, Cost: 50025.0
    DStock: AAPL, Shares: 50, Price: 142.50, Cost: 7125.00
    Error updating DStock price: Expected Decimal
    Updated DStock price: 155.25, Cost: 7762.50

Résumé

Dans ce labo (lab), vous avez appris à utiliser les attributs privés, à convertir des méthodes en propriétés (properties), à implémenter la validation de propriété (property validation), à utiliser __slots__ pour l'optimisation de la mémoire et à concilier la validation de type avec les variables de classe. Ces techniques améliorent la robustesse, l'efficacité et la maintenabilité de vos classes en renforçant l'encapsulation et en fournissant des interfaces claires.