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.
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 :
Ouvrez le fichier
stock.pydans l'éditeur.Modifiez la variable de classe
typesen 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...Mettez à jour la méthode
from_rowpour 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)Enregistrez le fichier
stock.py.Créez un script Python nommé
test_stock.pypour tester vos modifications. Vous pouvez créer le fichier dans l'éditeur en utilisant la commande suivante :touch /home/labex/project/test_stock.pyAjoutez le code suivant au fichier
test_stock.py. Ce code crée des instances de la classeStocket 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()}")Exécutez le script de test en utilisant la commande suivante dans le terminal :
python /home/labex/project/test_stock.pyVous 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 :
Ouvrez le fichier
stock.pydans l'éditeur.Remplacez la méthode
cost()par une propriété en utilisant le décorateur@property:@property def cost(self): return self.shares * self.priceEnregistrez le fichier
stock.py.Créez un nouveau fichier nommé
test_property.pydans l'éditeur :touch /home/labex/project/test_property.pyAjoutez le code suivant au fichier
test_property.pypour créer une instanceStocket 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 propertyExécutez le script de test :
python /home/labex/project/test_property.pyVous devriez voir une sortie similaire à :
Stock: GOOG Shares: 100 Price: 490.1 Cost: 49010.0
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 :
Ouvrez le fichier
stock.pydans l'éditeur.Ajoutez les attributs privés
_shareset_priceà la classeStocket 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 attributeDéfinissez des propriétés pour
sharesetpriceavec 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 = valueMettez à 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 setterEnregistrez le fichier
stock.py.Créez un script de test nommé
test_validation.py:touch /home/labex/project/test_validation.pyAjoutez 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}")Exécutez le script de test :
python /home/labex/project/test_validation.pyVous 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
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 :
- Restreindre la création d'attributs aux seuls attributs que nous avons définis.
- Améliorer l'efficacité de la mémoire, en particulier lors de la création de nombreuses instances.
Instructions :
Ouvrez le fichier
stock.pydans l'éditeur.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...Enregistrez le fichier.
Créez un script de test nommé
test_slots.py:touch /home/labex/project/test_slots.pyAjoutez 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}")Exécutez le script de test :
python /home/labex/project/test_slots.pyVous 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'
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 :
Ouvrez le fichier
stock.pydans l'éditeur.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 = valueEnregistrez le fichier
stock.py.Créez un script de test nommé
test_subclass.py:touch /home/labex/project/test_subclass.pyAjoutez 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}")Exécutez le script de test :
python /home/labex/project/test_subclass.pyVous devriez voir que la classe de base
Stockaccepte les valeurs float pour le prix, tandis que la sous-classeDStockexige des valeursDecimal.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.