Ceci est un texte traduit automatiquement qui peut contenir des erreurs !
En Python (cela s’applique également à la plupart des autres langages), il est possible de créer ses propres objets, avec leurs propres valeurs, règles et fonctions. On appelle cela des classes (classes en anglais). Nous utilisons les classes pour regrouper des données et des fonctions liées dans une seule unité, par exemple une classe Ordre qui possède des données telles que ordre_id, kunde_navn, produkter et des fonctions telles que 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 allons examiner comment utiliser les classes pour valider les données.
Le plus simple est d’utiliser @dataclass de la bibliothèque dataclasses. Cela nous évite d’écrire beaucoup de code boilerplate pour créer une classe. (Comme 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 permet d’obtenir 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 Personne
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 une Personne 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 post_init
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 une validation simple d’e-mail
Voici une solution possible, nous utilisons des exceptions pour faire “planter” le programme si l’e-mail est invalide, ce qui 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'espaces : {self.address}")
# Tester le code
test = Email("hei@example.com") # Valide
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Invalide, manque de @
Exercice 3 - Validation des numéros de téléphone
Créez une classe similaire à celle que vous avez faite pour les adresses e-mail, mais maintenant pour les numéros de téléphone.
Problème avec la validation téléphonique !
Pouvez-vous corriger la validation des numéros de téléphone pour qu’elle accepte à la fois les lettres (str) et les chiffres (int) ? Par exemple, 12345678 et "12345678" doivent tous deux être valides.
Essayez également d’ajouter les indicatifs de pays comme un attribut (sous-valeur de la classe). Par exemple, 47 pour la Norvège, 46 pour la Suède
Exercice 4 - Utiliser la validation dans la classe Person
Maintenant que nous avons créé la validation pour l’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
Nouveau défi se présente !
Maintenant que nous avons modifié la classe Person pour utiliser les classes PhoneNumber et Email, nous devons également modifier la façon dont nous instancions (créons) une Person. Nous devons maintenant 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 comme les adresses e-mail et les numéros de téléphone, nous sommes obligés de 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 voulons extraire la valeur. Cela peut devenir un peu lourd à la longue. Heureusement, il existe une solution à cela, en utilisant le décorateur @property dans une classe, ce qui nous permet d’extraire la valeur directement de l’objet, sans avoir à spécifier la sous-valeur.
Cela pose cependant un autre défi, et c’est que nous avons besoin de la fonction __init__ dans la classe Person. Cela est dû au fait que nous ne pouvons pas utiliser le même nom à la fois pour 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 la sous-valeur directement
# Test du code
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Output: Noe tekst
Merk
Les propriétés sont uniques dans le fait qu’elles n’ont pas besoin de paramètres et n’ont pas besoin de parenthèses pour être exécutées. Dans l’exemple, nous récupérons person.verdi sans parenthèses (pas person.verdi()), même si techniquement il s’agit d’une fonction.
Exemple alternatif acceptant à 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 la sous-valeur directement
# 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 : Propriétés Age et Adulte
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 en fonction de la date de naissance et de la date d'aujourd'hui"""
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
# Tester le code
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Par exemple, 18 si la date d'aujourd'hui est après le 15 mai 2023
print(person.is_adult) # True
Encore un défi !
Pouvez-vous faire en sorte que l’instanciation de Person accepte à la fois datetime.date et une chaîne de texte au format "JJ-MM-AAAA" pour birthday ? (Indice : utilisez datetime.strptime pour convertir la chaîne en datetime.date)
