Validation des données et classes

Skip to content

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

Développeurs Python britanniques en train de dire que c'est un constructeur, __init__ ?

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)

Easy 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 personne
  • eye_color : La couleur des yeux de la personne
  • phone_number : Le numéro de téléphone de la personne
  • email : 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

Medium 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 @

Medium 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

Medium 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(?)

Hard 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

Hard 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é age qui calcule l’âge de la personne en fonction de birthday et de la date actuelle.
  • Créez une propriété is_adult qui renvoie True si la personne a 18 ans ou plus, sinon False.
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)