Validation des données et classes

Skip to content

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. Cela s’appelle des classes (classes en anglais). Nous utilisons des classes pour regrouper des données et des fonctions connexes en une seule unité, par exemple une classe Ordre qui contient des données telles que ordre_id, kunde_navn, produkter et des fonctions telles que legg_til_produkt(), beregn_total(), etc.

Dictionary (JSON) vs Klasser (Objekter)

JSON (JavaScript Object Notation) est un format pour 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 sur le réseau, ou les stocker dans un fichier, nous utilisons souvent JSON (ou des tables dans des bases de données).

Lorsque nous devons travailler 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

British python devs be like thats a constructor, __init__?

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)

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)

Løsning: En dataclass for 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 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 facilement entraîner une dette technique à l’avenir. Heureusement, il existe des moyens simples d’ajouter une validation aux classes.

Nous allons commencer par examiner la validation des adresses e-mail. Il existe des bibliothèques intégrées en Python qui peuvent nous aider à ce faire, mais comme nous faisons cela pour apprendre, nous allons créer notre propre validation simple en créant une nouvelle classe “Email” et en examinant une fonction __post_init__ (uniquement pour dataclass).

Merk

Nous pouvons également 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 qui peuvent être réutilisés.

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 !)

Løsning: Kode for enkel e-post validering

Voici une solution possible, nous utilisons des exceptions pour “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"Mangler @ i epostadressen: {self.address}")
        if "." not in self.address.split("@")[1]:
            raise ValueError(f"Mangler . i domenet til epostadressen: {self.address}")
        if " " in self.address:
            raise ValueError(f"Epostadressen kan ikke inneholde mellomrom: {self.address}")

# Test koden 
test = Email("hei@example.com")  # Gyldig
try:
    test = Email("heiexample.com")
except ValueError as e:
    print(e) # Ugyldig, mangler @

Medium 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

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

Hard 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 en ce sens 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 s’il s’agit 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 må være av type str eller EksempelVerdi")

    @property
    def verdi(self) -> str:
        return self._verdi.attributt  # Henter ut underverdien direkte

# Test koden
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi)  # Output: Noe tekst

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 : 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)