Ceci est un texte traduit automatiquement qui peut contenir des erreurs !
En Python (valable également pour la plupart des autres langages), il est possible de créer ses propres objets, avec leurs propres valeurs, règles et fonctions. Cela s’appelle les classes (classes en anglais). Nous utilisons les classes pour regrouper des données et des fonctions liées dans une unité unique, par exemple une classe Ordre qui possède des données telles que ordre_id, kunde_navn, produkter ainsi que des fonctions comme legg_til_produkt(), beregn_total(), etc.
Dictionnaire (JSON) vs Classes (Objets)
JSON (JavaScript Object Notation) est un format permettant de stocker et transférer des données indépendamment du langage de programmation, tandis qu’une classe est une structure dans un langage de programmation spécifique.
Lorsque nous devons envoyer des données via le réseau, ou les enregistrer dans un fichier, nous utilisons souvent JSON (ou des tables dans des bases de données).
Lorsque nous travaillons avec des données structurées dans le code, nous utilisons des classes.
Dans ce module, nous examinerons comment utiliser les classes pour valider des données.
Vidéo : Comment les classes collectent des données et des règles
La vidéo explique ce qu’est une classe et comment les classes peuvent être utilisées pour structurer et valider des données.
Le plus simple est d’utiliser @dataclass à partir du module dataclasses. Cela nous permet d’éviter d’écrire beaucoup de code répétitif pour créer une classe. (Par exemple, les fonctions intégrées __init__ et __repr__ (représentation)).
Exemple d’une classe sans utiliser le décorateur dataclass :
class Car:
def __init__(self, make: str, model: str, year: int):
self.make = make
self.model = model
self.year = year
def __repr__(self):
return f"{self.year} {self.make} {self.model}"
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Sortie : 2020 Toyota Corolla
Exemple avec le décorateur dataclass, qui atteint le même résultat que ci-dessus (mais avec moins de code) :
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Sortie : Car(make='Toyota', model='Corolla', year=2020)
Exercice 1 - Créer une classe
Créez une classe nommée Person. Cette classe doit avoir les attributs suivants :
name: Le nom de la personneeye_color: La couleur des yeux de la personnephone_number: Le numéro de téléphone de la personneemail: L’adresse e-mail de la personne
Instanciez (utilisez) un objet de la classe Person avec des valeurs valides pour tous les attributs, comme dans l’exemple ci-dessous.
@dataclass
class Person:
... # Votre code ici
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number="12345678",
email="bob_kaare@example.com")
print(bob_kaare)
Solution : Une dataclass pour Person
Voici une solution possible :
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Exercice 2 - Validation dans la classe
Dans l’exemple ci-dessus, nous n’avons pas ajouté de validation. Cela signifie que nous pouvons créer un objet Person avec des valeurs invalides, telles que :
@dataclass
class Person:
... # Votre code ici
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# Output: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
Ceci est (potentiellement) problématique et peut rapidement accumuler de la dette technique à l’avenir. Heureusement, il existe des moyens simples d’ajouter de la validation dans les classes.
Nous allons commencer par examiner la validation d’e-mail. Il existe des bibliothèques intégrées en Python qui peuvent nous aider pour cela, mais parce que nous faisons cela pour apprendre, nous allons créer notre propre validation simple, en créant une nouvelle classe pour “Email” et en examinant une fonction __post_init__ (uniquement pour les dataclasses).
Note
Nous pouvons aussi faire cela dans la classe Person elle-même, mais il est souvent préférable de créer des classes distinctes pour les éléments réutilisables.
Exemple d’une fonction _postinit
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validating email: {self.address}")
# Votre code ici
Pour une simple validation d’e-mail, nous pouvons par exemple vérifier que l’e-mail contient à la fois les caractères @ et .. Éventuellement, vous pouvez vérifier que l’e-mail correspond à un modèle regex. (Plus avancé, mais n’hésitez pas à chercher sur le web !)
Solution : Code pour la validation simple d’e-mail
Voici une solution possible, nous utilisons des exceptions pour « crasher » si l’adresse e-mail est invalide, cela arrêtera le programme immédiatement et affichera un message d’erreur.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Manque de @ dans l'adresse e-mail : {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Manque de . dans le domaine de l'adresse e-mail : {self.address}")
if " " in self.address:
raise ValueError(f"L'adresse e-mail ne peut pas contenir d'espace : {self.address}")
# Test du code
test = Email("hei@example.com") # Valable
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Invalide, manque de @
Exercice 3 - Validation de numéro de téléphone
Créez une classe similaire à celle que vous avez faite pour les adresses e-mail, mais cette fois-ci pour les numéros de téléphone
Défi de validation téléphonique !
Pouvez-vous corriger la validation des numéros de téléphone pour accepter à la fois les lettres (chaîne) et les chiffres (entier) ? Par exemple, 12345678 et "12345678" doivent tous deux être valides.
Essayez également d’ajouter un indicatif pays comme une attribution (sous-valeur de la classe). Par exemple, 47 pour la Norvège, 46 pour la Suède
Exercice 4 - Utilisation de la validation dans la classe Person
Maintenant que nous avons créé une validation pour l’adresse e-mail et le numéro de téléphone, nous pouvons les utiliser dans la classe Person.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Utilisez la classe PhoneNumber
email: Email # Utilisez la classe Email
Un nouveau défi apparaît !
Maintenant que nous avons modifié la classe Person pour utiliser les classes PhoneNumber et Email, nous devons également modifier la façon dont nous instantions (créons) une Person. Nous devons d’abord créer un objet PhoneNumber et un objet Email, avant de pouvoir créer une Person.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Remarquez le changement ici
email=Email("bob_kaare@example.com")) # Remarquez le changement ici
print(bob_kaare)
# Remarque : une modification doit être apportée à la manière dont nous récupérons les valeurs également
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Exercice 5 - Propriétés dans les classes (Facultatif)
Lorsque nous utilisons des objets pour représenter des valeurs telles que l’adresse électronique et le numéro de téléphone, nous devons spécifier la sous-valeur (par exemple address pour l’e-mail et number pour le numéro de téléphone) à chaque fois que nous récupérons la valeur. Cela peut devenir un peu fastidieux sur la durée. Heureusement, il existe une solution : utiliser le décorateur @property dans une classe, ce qui permet d’accéder directement à la valeur depuis l’objet sans avoir à spécifier la sous-valeur.
Cependant, cela soulève encore un défi supplémentaire : nous avons besoin de la fonction __init__ dans la classe Person. En effet, nous ne pouvons pas utiliser le même nom pour à la fois une propriété et un attribut dans une dataclass.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Variable interne (commence par _ pour indiquer qu'elle est "privée")
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Récupère directement la sous-valeur
# Tester le code
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Sortie : Noe tekst
Note
Les propriétés sont uniques car elles ne nécessitent pas de paramètres et n’ont pas besoin d’être parenthésées pour être exécutées. Dans l’exemple, nous extrayons person.value sans parentheses (pas person.value()) bien qu’il s’agisse techniquement d’une fonction.
Exemple alternatif qui accepte à la fois str et EksempelVerdi
@dataclass
class Person:
name: str
_verdi: str
def __init__(self, name: str, verdi: str | EksempelVerdi):
self.name = name
if isinstance(verdi, EksempelVerdi):
self._verdi = verdi
elif isinstance(verdi, str):
self._verdi = EksempelVerdi(verdi)
else:
raise TypeError("verdi doit être de type str ou EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Récupère directement la valeur interne
# Tester le code
person = Person(name="Alice", verdi="Du texte")
print(person.verdi) # Sortie : Du texte
Exercice 6 - Propriétés avec logique (Facultatif)
Mettez à jour Person en ajoutant un nouvel attribut appelé birthday (date de naissance). Celui-ci doit être de type datetime.date (depuis la bibliothèque datetime).
Créez ensuite les propriétés suivantes :
- Créez une propriété
agequi calcule l’âge de la personne en fonction debirthdayet de la date actuelle. - Créez une propriété
is_adultqui renvoieTruesi la personne a 18 ans ou plus, sinonFalse.
Solution : Âge et adulte en tant que propriétés
Voici une solution possible :
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Calcule l'âge basé sur la date de naissance et la date du jour"""
today = date.today()
age = today.year - self.birthday.year
# Ajuster vers le bas de 1 si la personne n'a pas encore eu son anniversaire cette année
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Retourne True si la personne a 18 ans ou plus"""
return self.age >= 18
# Testez le code
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Par ex. 18 si la date d'aujourd'hui est après le 15 mai 2023
print(person.is_adult) # True
Un autre défi !
Parviens-tu à faire accepter l’instanciation de Person à la fois un objet datetime.date et une chaîne au format "JJ-MM-AAAA" pour birthday ? (Astuce : utilise datetime.strptime pour convertir la chaîne en datetime.date)
