Introduction
Dans ce laboratoire, vous allez apprendre à connaître les variables de classe et les méthodes de classe en Python. Vous comprendrez leur objectif et leur utilisation, et apprendrez à définir et à utiliser efficacement les méthodes de classe.
De plus, vous allez implémenter des constructeurs alternatifs en utilisant des méthodes de classe, explorer la relation entre les variables de classe et l'héritage, et créer des utilitaires de lecture de données flexibles. Les fichiers stock.py et reader.py seront modifiés au cours de ce laboratoire.
Comprendre les variables de classe et les méthodes de classe
Dans cette première étape, nous allons plonger dans les concepts de variables de classe et de méthodes de classe en Python. Ce sont des concepts importants qui vous aideront à écrire un code plus efficace et mieux organisé. Avant de commencer à travailler avec les variables de classe et les méthodes de classe, regardons d'abord comment les instances de notre classe Stock sont actuellement créées. Cela nous donnera une compréhension de base et nous montrera où nous pouvons apporter des améliorations.
Quelles sont les variables de classe ?
Les variables de classe sont un type spécial de variables en Python. Elles sont partagées entre toutes les instances d'une classe. Pour mieux comprendre cela, comparons-les avec les variables d'instance. Les variables d'instance sont uniques à chaque instance d'une classe. Par exemple, si vous avez plusieurs instances d'une classe, chaque instance peut avoir sa propre valeur pour une variable d'instance. En revanche, les variables de classe sont définies au niveau de la classe. Cela signifie que toutes les instances de cette classe peuvent accéder et partager la même valeur de la variable de classe.
Quelles sont les méthodes de classe ?
Les méthodes de classe sont des méthodes qui opèrent sur la classe elle-même, et non sur les instances individuelles de la classe. Elles sont liées à la classe, ce qui signifie qu'elles peuvent être appelées directement sur la classe sans créer d'instance. Pour définir une méthode de classe en Python, nous utilisons le décorateur @classmethod. Et au lieu de prendre l'instance (self) comme premier paramètre, les méthodes de classe prennent la classe (cls) comme premier paramètre. Cela leur permet d'opérer sur les données au niveau de la classe et d'effectuer des actions liées à la classe dans son ensemble.
Approche actuelle de la création d'instances de Stock
Voyons d'abord comment nous créons actuellement des instances de la classe Stock. Ouvrez le fichier stock.py dans l'éditeur pour observer l'implémentation actuelle :
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
Les instances de cette classe sont généralement créées de l'une des manières suivantes :
Initialisation directe avec des valeurs :
s = Stock('GOOG', 100, 490.1)Ici, nous créons directement une instance de la classe
Stocken fournissant les valeurs pour les attributsname,sharesetprice. C'est un moyen simple de créer une instance lorsque vous connaissez les valeurs à l'avance.Création à partir de données lues dans un fichier CSV :
import csv with open('portfolio.csv') as f: rows = csv.reader(f) headers = next(rows) ## Skip the header row = next(rows) ## Get the first data row s = Stock(row[0], int(row[1]), float(row[2]))Lorsque nous lisons des données dans un fichier CSV, les valeurs sont initialement au format chaîne de caractères. Donc, lorsque nous créons une instance de
Stockà partir de données CSV, nous devons convertir manuellement les valeurs de chaîne en types appropriés. Par exemple, la valeur desharesdoit être convertie en entier, et la valeur depricedoit être convertie en nombre à virgule flottante.
Essayons cela. Créez un nouveau fichier Python appelé test_stock.py dans le répertoire ~/project avec le contenu suivant :
## test_stock.py
from stock import Stock
import csv
## Méthode 1 : Création directe
s1 = Stock('GOOG', 100, 490.1)
print(f"Stock: {s1.name}, Shares: {s1.shares}, Price: {s1.price}")
print(f"Cost: {s1.cost()}")
## Méthode 2 : Création à partir d'une ligne CSV
with open('portfolio.csv') as f:
rows = csv.reader(f)
headers = next(rows) ## Skip the header
row = next(rows) ## Get the first data row
s2 = Stock(row[0], int(row[1]), float(row[2]))
print(f"\nStock from CSV: {s2.name}, Shares: {s2.shares}, Price: {s2.price}")
print(f"Cost: {s2.cost()}")
Exécutez ce fichier pour voir les résultats :
cd ~/project
python test_stock.py
Vous devriez voir une sortie similaire à :
Stock: GOOG, Shares: 100, Price: 490.1
Cost: 49010.0
Stock from CSV: AA, Shares: 100, Price: 32.2
Cost: 3220.0
Cette conversion manuelle fonctionne, mais elle présente certains inconvénients. Nous devons connaître le format exact des données, et nous devons effectuer les conversions chaque fois que nous créons une instance à partir de données CSV. Cela peut être source d'erreurs et chronophage. Dans l'étape suivante, nous allons créer une solution plus élégante en utilisant des méthodes de classe.
Implémentation de constructeurs alternatifs avec des méthodes de classe
Dans cette étape, nous allons apprendre à implémenter un constructeur alternatif en utilisant une méthode de classe. Cela nous permettra de créer des objets Stock à partir de données de lignes CSV d'une manière plus élégante.
Qu'est-ce qu'un constructeur alternatif ?
En Python, un constructeur alternatif est un modèle utile. Habituellement, nous créons des objets en utilisant la méthode standard __init__. Cependant, un constructeur alternatif nous offre un moyen supplémentaire de créer des objets. Les méthodes de classe sont très appropriées pour implémenter des constructeurs alternatifs car elles peuvent accéder à la classe elle - même.
Implémentation de la méthode de classe from_row()
Nous allons ajouter une variable de classe types et une méthode de classe from_row() à notre classe Stock. Cela simplifiera le processus de création d'instances de Stock à partir de données CSV.
Modifions le fichier stock.py en ajoutant le code mis en évidence :
## stock.py
class Stock:
types = (str, int, float) ## Type conversions to apply to CSV data
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
@classmethod
def from_row(cls, row):
"""
Create a Stock instance from a row of CSV data.
Args:
row: A list of strings [name, shares, price]
Returns:
A new Stock instance
"""
values = [func(val) for func, val in zip(cls.types, row)]
return cls(*values)
## The rest of the file remains unchanged
Maintenant, comprenons étape par étape ce qui se passe dans ce code :
- Nous avons défini une variable de classe
types. C'est un tuple qui contient des fonctions de conversion de type(str, int, float). Ces fonctions seront utilisées pour convertir les données de la ligne CSV en types appropriés. - Nous avons ajouté une méthode de classe
from_row(). Le décorateur@classmethodmarque cette méthode comme une méthode de classe. - Le premier paramètre de cette méthode est
cls, qui est une référence à la classe elle - même. Dans les méthodes normales, nous utilisonsselfpour faire référence à une instance de la classe, mais ici nous utilisonsclscar il s'agit d'une méthode de classe. - La fonction
zip()est utilisée pour associer chaque fonction de conversion de type danstypesà la valeur correspondante dans la listerow. - Nous utilisons une compréhension de liste pour appliquer chaque fonction de conversion à la valeur correspondante dans la liste
row. De cette façon, nous convertissons les données de chaîne de caractères de la ligne CSV en types appropriés. - Enfin, nous créons une nouvelle instance de la classe
Stocken utilisant les valeurs converties et la retournons.
Test du constructeur alternatif
Maintenant, nous allons créer un nouveau fichier appelé test_class_method.py pour tester notre nouvelle méthode de classe. Cela nous aidera à vérifier que le constructeur alternatif fonctionne comme prévu.
## test_class_method.py
from stock import Stock
## Test the from_row() class method
row = ['AA', '100', '32.20']
s = Stock.from_row(row)
print(f"Stock: {s.name}")
print(f"Shares: {s.shares}")
print(f"Price: {s.price}")
print(f"Cost: {s.cost()}")
## Try with a different row
row2 = ['GOOG', '50', '1120.50']
s2 = Stock.from_row(row2)
print(f"\nStock: {s2.name}")
print(f"Shares: {s2.shares}")
print(f"Price: {s2.price}")
print(f"Cost: {s2.cost()}")
Pour voir les résultats, exécutez les commandes suivantes dans votre terminal :
cd ~/project
python test_class_method.py
Vous devriez voir une sortie similaire à ceci :
Stock: AA
Shares: 100
Price: 32.2
Cost: 3220.0
Stock: GOOG
Shares: 50
Price: 1120.5
Cost: 56025.0
Notez que maintenant nous pouvons créer des instances de Stock directement à partir de données de chaîne de caractères sans avoir à effectuer manuellement des conversions de type en dehors de la classe. Cela rend notre code plus propre et assure que la responsabilité de la conversion des données est gérée au sein de la classe elle - même.
Variables de classe et héritage
Dans cette étape, nous allons explorer comment les variables de classe interagissent avec l'héritage et en quoi elles peuvent servir de mécanisme de personnalisation. En Python, l'héritage permet à une sous - classe d'hériter d'attributs et de méthodes d'une classe de base. Les variables de classe sont des variables qui appartiennent à la classe elle - même, et non à une instance spécifique de la classe. Comprendre comment ces éléments fonctionnent ensemble est crucial pour créer un code flexible et maintenable.
Variables de classe dans l'héritage
Lorsqu'une sous - classe hérite d'une classe de base, elle a automatiquement accès aux variables de classe de la classe de base. Cependant, une sous - classe peut remplacer (override) ces variables de classe. En faisant cela, la sous - classe peut modifier son comportement sans affecter la classe de base. C'est une fonctionnalité très puissante car elle vous permet de personnaliser le comportement d'une sous - classe selon vos besoins spécifiques.
Création d'une classe Stock spécialisée
Créons une sous - classe de la classe Stock. Nous l'appellerons DStock, qui signifie Decimal Stock (Stock décimal). La principale différence entre DStock et la classe Stock normale est que DStock utilisera le type Decimal pour les valeurs de prix au lieu de float. Dans les calculs financiers, la précision est extrêmement importante, et le type Decimal offre des opérations arithmétiques décimales plus précises que le type float.
Pour créer cette sous - classe, nous allons créer un nouveau fichier nommé decimal_stock.py. Voici le code que vous devez placer dans ce fichier :
## decimal_stock.py
from decimal import Decimal
from stock import Stock
class DStock(Stock):
"""
A specialized version of Stock that uses Decimal for prices
"""
types = (str, int, Decimal) ## Override the types class variable
## Test the subclass
if __name__ == "__main__":
## Create a DStock from row data
row = ['AA', '100', '32.20']
ds = DStock.from_row(row)
print(f"DStock: {ds.name}")
print(f"Shares: {ds.shares}")
print(f"Price: {ds.price} (type: {type(ds.price).__name__})")
print(f"Cost: {ds.cost()} (type: {type(ds.cost()).__name__})")
## For comparison, create a regular Stock from the same data
s = Stock.from_row(row)
print(f"\nRegular Stock: {s.name}")
print(f"Shares: {s.shares}")
print(f"Price: {s.price} (type: {type(s.price).__name__})")
print(f"Cost: {s.cost()} (type: {type(s.cost()).__name__})")
Après avoir créé le fichier decimal_stock.py avec le code ci - dessus, vous devez l'exécuter pour voir les résultats. Ouvrez votre terminal et suivez ces étapes :
cd ~/project
python decimal_stock.py
Vous devriez voir une sortie similaire à ceci :
DStock: AA
Shares: 100
Price: 32.20 (type: Decimal)
Cost: 3220.0 (type: Decimal)
Regular Stock: AA
Shares: 100
Price: 32.2 (type: float)
Cost: 3220.0 (type: float)
Points clés sur les variables de classe et l'héritage
À partir de cet exemple, nous pouvons tirer plusieurs conclusions importantes :
- La classe
DStockhérite de toutes les méthodes de la classeStock, comme la méthodecost(), sans avoir à les redéfinir. C'est l'un des principaux avantages de l'héritage, car cela vous évite d'écrire du code redondant. - En simplement remplaçant la variable de classe
types, nous avons modifié la façon dont les données sont converties lors de la création de nouvelles instances deDStock. Cela montre comment les variables de classe peuvent être utilisées pour personnaliser le comportement d'une sous - classe. - La classe de base,
Stock, reste inchangée et fonctionne toujours avec des valeurs de typefloat. Cela signifie que les modifications que nous avons apportées à la sous - classe n'affectent pas la classe de base, ce qui est un bon principe de conception. - La méthode de classe
from_row()fonctionne correctement avec les classesStocketDStock. Cela démontre la puissance de l'héritage, car la même méthode peut être utilisée avec différentes sous - classes.
Cet exemple montre clairement comment les variables de classe peuvent être utilisées comme mécanisme de configuration. Les sous - classes peuvent remplacer ces variables pour personnaliser leur comportement sans avoir à réécrire les méthodes.
Discussion sur la conception
Considérons une approche alternative où nous plaçons les conversions de type dans la méthode __init__ :
class Stock:
def __init__(self, name, shares, price):
self.name = str(name)
self.shares = int(shares)
self.price = float(price)
Avec cette approche, nous pouvons créer un objet Stock à partir d'une ligne de données comme ceci :
row = ['AA', '100', '32.20']
s = Stock(*row)
Bien que cette approche puisse sembler plus simple à première vue, elle présente plusieurs inconvénients :
- Elle combine deux préoccupations différentes : l'initialisation de l'objet et la conversion des données. Cela rend le code plus difficile à comprendre et à maintenir.
- La méthode
__init__devient moins flexible car elle convertit toujours les entrées, même si elles sont déjà au bon type. - Elle restreint la façon dont les sous - classes peuvent personnaliser le processus de conversion. Les sous - classes auraient plus de difficulté à modifier la logique de conversion si elle est intégrée dans la méthode
__init__. - Le code devient plus fragile. Si l'une des conversions échoue, l'objet ne peut pas être créé, ce qui peut entraîner des erreurs dans votre programme.
D'un autre côté, l'approche basée sur les méthodes de classe sépare ces préoccupations. Cela rend le code plus maintenable et flexible, car chaque partie du code a une seule responsabilité.
Création d'un lecteur CSV polyvalent
Dans cette étape finale, nous allons créer une fonction polyvalente. Cette fonction sera capable de lire des fichiers CSV et de créer des objets de n'importe quelle classe qui a implémenté la méthode de classe from_row(). Cela montre la puissance de l'utilisation des méthodes de classe comme interface uniforme. Une interface uniforme signifie que différentes classes peuvent être utilisées de la même manière, ce qui rend notre code plus flexible et plus facile à gérer.
Modification de la fonction read_portfolio()
Tout d'abord, nous allons mettre à jour la fonction read_portfolio() dans le fichier stock.py. Nous utiliserons notre nouvelle méthode de classe from_row(). Ouvrez le fichier stock.py et modifiez la fonction read_portfolio() comme ceci :
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of Stock instances
'''
import csv
portfolio = []
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows) ## Skip header
for row in rows:
portfolio.append(Stock.from_row(row))
return portfolio
Cette nouvelle version de la fonction est plus simple. Elle confie la responsabilité de la conversion de type à la classe Stock, là où elle appartient vraiment. La conversion de type consiste à changer les données d'un type à un autre, comme transformer une chaîne de caractères en entier. En faisant cela, nous rendons notre code plus organisé et plus facile à comprendre.
Création d'un lecteur CSV polyvalent
Maintenant, nous allons créer une fonction plus polyvalente dans le fichier reader.py. Cette fonction peut lire des données CSV et créer des instances de n'importe quelle classe qui a une méthode de classe from_row().
Ouvrez le fichier reader.py et ajoutez la fonction suivante :
def read_csv_as_instances(filename, cls):
'''
Read a CSV file into a list of instances of the given class.
Args:
filename: Name of the CSV file
cls: Class to instantiate (must have from_row class method)
Returns:
List of class instances
'''
records = []
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows) ## Skip header
for row in rows:
records.append(cls.from_row(row))
return records
Cette fonction prend deux entrées : un nom de fichier et une classe. Elle retourne ensuite une liste d'instances de cette classe, créées à partir des données du fichier CSV. Cela est très utile car nous pouvons l'utiliser avec différentes classes, tant qu'elles ont la méthode from_row().
Test du lecteur CSV polyvalent
Créons un fichier de test pour voir comment fonctionne notre lecteur polyvalent. Créez un fichier nommé test_csv_reader.py avec le contenu suivant :
## test_csv_reader.py
from reader import read_csv_as_instances
from stock import Stock
from decimal_stock import DStock
## Read portfolio as Stock instances
portfolio = read_csv_as_instances('portfolio.csv', Stock)
print(f"Portfolio contains {len(portfolio)} stocks")
print(f"First stock: {portfolio[0].name}, {portfolio[0].shares} shares at ${portfolio[0].price}")
## Read portfolio as DStock instances (with Decimal prices)
decimal_portfolio = read_csv_as_instances('portfolio.csv', DStock)
print(f"\nDecimal portfolio contains {len(decimal_portfolio)} stocks")
print(f"First stock: {decimal_portfolio[0].name}, {decimal_portfolio[0].shares} shares at ${decimal_portfolio[0].price}")
## Define a new class for reading the bus data
class BusRide:
def __init__(self, route, date, daytype, rides):
self.route = route
self.date = date
self.daytype = daytype
self.rides = rides
@classmethod
def from_row(cls, row):
return cls(row[0], row[1], row[2], int(row[3]))
## Read some bus data (just the first 5 records for brevity)
print("\nReading bus data...")
import csv
with open('ctabus.csv') as f:
rows = csv.reader(f)
headers = next(rows) ## Skip header
bus_rides = []
for i, row in enumerate(rows):
if i >= 5: ## Only read 5 records for the example
break
bus_rides.append(BusRide.from_row(row))
## Display the bus data
for ride in bus_rides:
print(f"Route: {ride.route}, Date: {ride.date}, Type: {ride.daytype}, Rides: {ride.rides}")
Exécutez ce fichier pour voir les résultats. Ouvrez votre terminal et utilisez les commandes suivantes :
cd ~/project
python test_csv_reader.py
Vous devriez voir une sortie qui montre les données du portefeuille chargées sous forme d'instances Stock et DStock, ainsi que les données des itinéraires de bus chargées sous forme d'instances BusRide. Cela prouve que notre lecteur polyvalent fonctionne avec différentes classes.
Principaux avantages de cette approche
Cette approche montre plusieurs concepts puissants :
- Séparation des préoccupations : La lecture des données est séparée de la création des objets. Cela signifie que le code pour lire le fichier CSV n'est pas mélangé avec le code pour créer les objets. Cela rend le code plus facile à comprendre et à maintenir.
- Polymorphisme : Le même code peut fonctionner avec différentes classes qui suivent la même interface. Dans notre cas, tant qu'une classe a la méthode
from_row(), notre lecteur polyvalent peut l'utiliser. - Flexibilité : Nous pouvons facilement changer la façon dont les données sont converties en utilisant différentes classes. Par exemple, nous pouvons utiliser
StockouDStockpour gérer les données du portefeuille différemment. - Extensibilité : Nous pouvons ajouter de nouvelles classes qui fonctionnent avec notre lecteur sans modifier le code du lecteur. Cela rend notre code plus durable.
C'est un modèle courant en Python qui rend le code plus modulaire, réutilisable et maintenable.
Remarques finales sur les méthodes de classe
Les méthodes de classe sont souvent utilisées comme constructeurs alternatifs en Python. Vous pouvez généralement les reconnaître car leurs noms contiennent souvent le mot "from". Par exemple :
## Some examples from Python's built-in types
dict.fromkeys(['a', 'b', 'c'], 0) ## Create a dict with default values
datetime.datetime.fromtimestamp(1627776000) ## Create datetime from timestamp
int.from_bytes(b'\x00\x01', byteorder='big') ## Create int from bytes
En suivant cette convention, vous rendez votre code plus lisible et cohérent avec les bibliothèques intégrées de Python. Cela aide les autres développeurs à comprendre votre code plus facilement.
Résumé
Dans ce laboratoire, vous avez appris deux fonctionnalités cruciales de Python : les variables de classe et les méthodes de classe. Les variables de classe sont partagées entre toutes les instances de la classe et peuvent être utilisées pour la configuration. Les méthodes de classe opèrent sur la classe elle - même, marquées avec le décorateur @classmethod. Les constructeurs alternatifs, une utilisation courante des méthodes de classe, offrent différentes manières de créer des objets. L'héritage avec les variables de classe permet aux sous - classes de personnaliser leur comportement en les remplaçant, et l'utilisation des méthodes de classe peut permettre une conception de code flexible.
Ces concepts sont puissants pour créer un code Python bien organisé et flexible. En plaçant les conversions de type à l'intérieur de la classe et en fournissant une interface uniforme via les méthodes de classe, vous pouvez écrire des utilitaires plus polyvalents. Pour approfondir vos connaissances, vous pouvez explorer davantage de cas d'utilisation, créer des hiérarchies de classes et construire des pipelines de traitement de données complexes en utilisant les méthodes de classe.