Exercices corrigés

Dates

Soit la classe Date  définie par le diagramme de classe UML suivant :

Implémenter cette classe en Python.

 

Dans la méthode de construction de la classe, prévoir un dispositif pour éviter les dates impossibles (du genre 32/14/2020).
Dans ce cas, la création doit provoquer une erreur, chose possible grâce à l’instruction raise (documentation à rechercher !).

 

Ajouter une méthode __repr__  permettant d’afficher la date sous la forme "25 janvier 1989". Les noms des mois seront définis en tant qu’attributs de classe à l’aide d’un tuple.

 

Ajouter une méthode __lt__ qui permet de comparer deux dates.
L’expression d1 < d2 (d1 et d2 étant deux objets de type Date) doit grâce à cette méthode renvoyer True ou False .
Correction
#!/usr/bin/env python
# -*- coding: utf-8 -*-

class Date:
    mois = ("janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre")
    def __init__(self, jour, mois, annee):
        if 1 <= jour <= 31:
            self.jour = jour
        else:
            raise ValueError('Le jour doit être compris entre 1 et 31')
        if 1 <= mois <= 12:
            self.mois = mois
        else:
            raise ValueError('Le mois doit être compris entre 1 et 12')
        if annee > 1980:
            self.annee = annee
        else:
            raise ValueError("L'année doit être supérieure ou égale à 1980")
        
    def __repr__(self):
        return "%s %s %s" %(self.jour, Date.mois[self.mois-1], self.annee)
        
    def __lt__(self, d):
        if self.annee < d.annee:
            return True
        elif self.annee > d.annee:
            return False
        else:
            if self.mois < d.mois:
                return True
            elif self.mois > d.mois:
                return False
            else:
                return self.jour < d.jour
            
d1 = Date(30, 3, 2020)
d2 = Date(11, 3, 2020)
print(d1 < d2)

 

 


Chronomètre

Implémenter une classe Chrono, dont les objets ont un unique argument temps, de type nombre, initialement égal à zéro.
Ajouter une méthode __str__ (c’est le nom de la méthode automatiquement appelée par la fonction str()), qui renvoie la la valeur de l’attribut temps, suivi de la lettre "s" (pour seconde)
Ajouter une méthode avancer qui demande un argument t (en plus de l’objet lui-même !), et qui incrémente l’attribut temps de la valeur t.

À ce stade, le comportement du programme doit être le suivant :

>>> c = Chrono()
>>> c.avancer(12)
>>> c
12s

Le problème de ce type de chronomètre, c’est qu’il a peu de chance de donner une valeur de temps écoulé correcte ! Il faut utiliser un circuit spécial de l’ordinateur appelé timer.

Python permet d’accéder à un timer avec la méthode time()du module time, qui renvoie la valeur, en secondes, du temps écoulé depuis l’« époque » (1er janvier 1970 !).

>>> time.time()
1633535583.5518436
>>> time.time()
1633535585.1418965
Remplacer la méthode avancer par un méthode actualiser, qui met à jour la valeur de l’attribut temps avec la valeur du temps écoulé depuis la création de l’objet. Pour cela, il faut ajouter un attribut t_zero lors de la construction, initialisé avec la valeur time.time() à l’instant de l’instanciation de l’objet.
Modifier la méthode __str__ afin qu’elle mette le chronomètre à jour juste avant d’afficher sa valeur.

À ce stade, le comportement du programme doit être le suivant :

>>> c = Chrono()
>>> c
4s
>>> c
6s
Correction
import time

class Chrono:
    def __init__(self):
        self.temps = 0
        self._zero = time.time()

    def __str__(self):
        self.actualiser()
        return str(self.temps)+"s"

    def __repr__(self):
        return str(self)

    def actualiser(self):
        self.temps = time.time() - self._zero

c = Chrono()

 

 


 IMC

Écrire sous forme de diagramme de classe la définition de la classe Personne ayant trois attributs définissant certaines caractéristiques d’une personne réelle : taille, poids et age .
Cette classe aura :

  • une méthode imc() qui détermine l’IMC de la personne,
  • une méthode interpretation() qui affiche "Insuffisance pondérale" si l’IMC est inférieur ou égale à 18,5 et qui affiche "obésité" si l’IMC est supérieur ou égale à 30.
    Rappel : l’IMC (Indice de masse corporelle) est donné par la formule \(\frac{poids}{taille^2}\) avec le poids en kg et la taille en m.

Implémenter cette classe en Python.

Correction

class Personne:
    def __init__(self, taille, poids, age):
        self.taille = taille
        self.poids = poids
        self.age = age

    def imc(self):
        return self.poids/taille**2

    def interpretation(self):
        imc = self.imc()
        if imc <= 18.5:
            print("Insuffisance pondérale")
        elif imc >= 30:
            print("Obésité")

 

 


Utilisateurs

Pour réaliser un site Web sur lequel des utilisateurs doivent s’identifier (pour accéder à des ressources, faire des quiz, déposer des fichiers, ;.), il faut que, du coté du serveur, un programme gère ces utilisateurs sous forme d’objets. Le diagramme de la classe Utilisateur suivant présente les principaux attributs nécessaires :

Les données de chaque utilisateur seront enregistrés dans une base de données.

Connexion

La connexion (signin) sur le site nécessite pour l’utilisateur de saisir son identifiant et son mot de passe. Ces deux attributs sont donc absolument obligatoires : ils doivent être fournis au constructeur de la classe.

Implémenter la classe Utilisateur et son constructeur. Ce dernier attend deux arguments pour l’identifiant et le mot de passe.
Correction
class Utilisateur:
    def __init__(self, identifiant, mdpasse):
        self.identifiant = identifiant
        self.motdepasse = mdpasse

 

Enregistrement

Lors de l’enregistrement sur le site (signup), l’utilisateur est invité à donner également son nom, son prénom et son adresse email.

Ajouter ces 3 attributs à la classe (dans le constructeur, mais pas parmi ses paramètres). Par défaut, leurs valeurs sont des chaînes de caractères vides.
Correction
class Utilisateur:
    def __init__(self, identifiant, mdpasse):
        self.identifiant = identifiant
        self.motdepasse = mdpasse
        self.nom = ""
        self.prenom = ""
        self.email = ""

 

Validation du mot de passe

Afin de garantir la robustesse du mot de passe choisi par l’utilisateur, on exige que celui ci contienne :

  • au moins 8 caractères
  • au moins 1 caractère spécial
  • au moins 2 chiffres
  • au moins 2 lettres majuscules
  • au moins 3 lettres minuscules

Pour cela, on peut utiliser les expressions régulières.

La fonction est_valide ci-dessous renvoie True si le mot de passe fourni en argument respecte les conditions définies ci-dessus, et False dans le cas contraire :

import re

def est_valide(mdp)
    rx = re.compile(r'^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$')
    return True if rx.match(mdp) else False
Adapter cette fonction pour en faire une méthode de la classe Utilisateur. La variable rx étant identique pour tous les utilisateurs, il est judicieux de la définir comme un attribut de classe.
Correction
import re
class Utilisateur:
    rx = re.compile(r'^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$')
    def __init__(self, identifiant, mdpasse):
        self.identifiant = identifiant
        self.motdepasse = mdpasse
        self.nom = ""
        self.prenom = ""
        self.email = ""

    def est_valide(self, mdp):
        return True if Utilisateur.rx.match(mdp) else False

 

Pour éviter des effets de bord (modification du mot de passe sans passer par la validation par exemple), le mot de passe doit être enregistré dans un attribut privé, et son accès contrôlé par un accesseur et un mutateur.

Rendre privé l’attribut motdepasse (en le renommant __motdepasse – avec 2 "_") et implémenter son accesseur et son mutateur (qui doit s’assurer de la validité du mot de passe !). En cas de mot de passe non valide, le mutateur doit renvoyer False (True dans le cas contraire).
Correction
import re
class Utilisateur:
    rx = re.compile(r'^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$')
    def __init__(self, identifiant, mdpasse):
        self.identifiant = identifiant
        self.__motdepasse = mdpasse
        self.nom = ""
        self.prenom = ""
        self.email = ""

    def est_valide(self, mdp):
        return True if Utilisateur.rx.match(mdp) else False

    def get_motdepasse(self):
        return self.__motdepasse

    def set_motdepasse(self, mdp):
        if self.valider_mdp(mdp):
            self.__motdepasse = mdp
            return True
        return False

 

 

 

 

 


géOOmétrie

Le programme Python suivant est écrit en programmation procédurale (pas orientée objet).

géOOmétrie
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
   Ce programme permet :
   - d'afficher des figures géométriques (de tailles et de couleurs différentes)
   - de modifier leur position en les faisant glisser
   - de modifier leur couleur en cliquant dessus

   Mais il n'est pas orienté objet !
"""

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import matplotlib.patches as mpatches
from random import random, randint
from matplotlib import colors
import six

# Une liste de couleurs
couleurs = [c[1] for c in list(six.iteritems(colors.cnames))]
def couleur_aleatoire():
   return couleurs[randint(0, len(couleurs)-1)]

def dimension_aleatoire(maxi = 0.2):
   return max(0.1, maxi*random())

# Une figure, un système d'axes
fig, ax = plt.subplots()
ax.set_title('Faire glisser les formes ... ou changer leur couleur en cliquant')

# Des cercles
for i in range(5):
   ax.add_artist(mpatches.Circle((random(),random()), dimension_aleatoire(),
                                 color = couleur_aleatoire(),
                                 ec = "none", picker = True))

# Des rectangles
for i in range(5):
   ax.add_artist(mpatches.Rectangle((random(),random()),
                                    dimension_aleatoire(0.4), dimension_aleatoire(0.4),
                                    color = couleur_aleatoire(),
                                    ec = "none", picker = True))

# Variables globales
dx, dy = 0.0, 0.0 # Coordonnées relatives
ar = None # artiste (forme géométrique)
mvt = False # Pour indiquer qu'un mouvement a commencé

# Ce qui se produit quand une forme est sélectionnée
def quandChoix(event):
   global dx, dy, ar
   ar = event.artist

   if isinstance(ar, mpatches.Circle):
      xdata, ydata = ar.center
   elif isinstance(ar, mpatches.Rectangle):
      xdata, ydata = ar.xy
   dx, dy = event.mouseevent.xdata - xdata, event.mouseevent.ydata-ydata


# Ce qui se produit quand la souris se déplace
def quandMouvement(event):
   global mvt

   if event.inaxes is None: # Le mouvement est en dehors du système d'axes
      return
   if event.button != 1: # Le bouton de la souris n'est pas appuyé
      return
   if ar == None: # Pas de forme sélectionnée
      return

   mvt = True # Un mouvement a commencé
   x, y = event.xdata, event.ydata # Les coordonnées de la souris

   if isinstance(ar, mpatches.Circle):
      ar.center = (x-dx, y-dy)

   elif isinstance(ar, mpatches.Rectangle):
      ar.set_x(x-dx)
      ar.set_y(y-dy)

   fig.canvas.draw()


# Ce qui se produit quand le bouton de la souris est relâché
def quandRelache(event):
   global ar, mvt
   if not mvt and ar is not None:
      ar.set_color(couleur_aleatoire())
      fig.canvas.draw()
   ar = None
   mvt = False


# Connection du canvas avec des événements
fig.canvas.mpl_connect('motion_notify_event', quandMouvement)
fig.canvas.mpl_connect('pick_event', quandChoix)
fig.canvas.mpl_connect('button_release_event', quandRelache)

plt.show()

La programmation procédurale est constituée de fonctions sans liens particuliers entre elles, agissant sur des données dissociées, et cela peut mener rapidement à des difficultés en cas de modification de la structure des données. Dans le cas de géOOmétrie ce type de programmation impose de différencier les types de donnée (isinstance()) car les données des objets Circleet Rectangle ne sont pas structurées de la même manière.

 

Tester et analyser le programme fourni. Identifier les attributs et les méthodes communs à la classe Forme_geometrique, ou au contraire spécifiques à ses sous-classes (choisir parmi la liste ci-dessous et compléter le diagramme de classes ci-dessous).

 

Le réécrire en utilisant de la programmation orientée objet, et en respectant le diagramme de classe.
Ajouter une classe permettant de gérer de la même manière des polygones réguliers

 

Correction
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    Ce programme permet :
     - d'affichier des figures géométriques (de tailles et de couleurs différentes)
     - de modifier leur position en les faisant glisser
     - de modifier leur couleur en cliquant dessus
     
     POO !
     
"""

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import matplotlib.patches as mpatches
from random import random, randint
from matplotlib import colors
import six

# Une liste de couleurs
couleurs = [c[1] for c in list(six.iteritems(colors.cnames))]
def couleur_aleatoire():
    return couleurs[randint(0, len(couleurs)-1)]

def dimension_aleatoire(maxi = 0.2):
    return max(0.1, maxi*random())

# Une figure, un système d'axes
fig, ax = plt.subplots()
ax.set_title('Faire glisser les formes ... ou changer leur couleur en cliquant')


class Forme_geometrique:
    def changer_couleur(self, couleur):
        self.set_color(couleur)


class Cercle(Forme_geometrique, mpatches.Circle):
    def __init__(self):
        mpatches.Circle.__init__(self, (random(),random()), dimension_aleatoire(),
                                  color = couleur_aleatoire(),
                                  ec = "none", picker = True)
        
    def deplacer(self, x, y):
        self.center = (x, y)
        
    def obtenir_position(self):
        return self.center


class Rectangle(Forme_geometrique, mpatches.Rectangle):
    def __init__(self):
        mpatches.Rectangle.__init__(self, (random(),random()), 
                                     dimension_aleatoire(0.4), dimension_aleatoire(0.4), 
                                     color = couleur_aleatoire(),
                                     ec = "none", picker = True)
        
    def deplacer(self, x, y):
        self.set_x(x)
        self.set_y(y)
        
    def obtenir_position(self):
        return self.xy
        
        
# Des cercles
for i in range(5):
    ax.add_artist(Cercle())

# Des rectangles
for i in range(5):
    ax.add_artist(Rectangle())

# Variables globales
dx, dy = 0.0, 0.0   # Coordonnées relatives
ar = None           # artiste (forme géométrique)
mvt = False         # Pour indiquer qu'un mouvement a commencé

# Ce qui se produit quand une forme est sélectionnée 
def quandChoix(event):
    global dx, dy, ar
    ar = event.artist

    xdata, ydata = ar.obtenir_position()
    dx, dy = event.mouseevent.xdata - xdata, event.mouseevent.ydata-ydata


# Ce qui se produit quand la souris se déplace
def quandMouvement(event):
    global mvt
    
    if event.inaxes is None:    # Le mouvement est en dehors du système d'axes
        return
    if event.button != 1:       # Le bouton de la souris n'est pas appuyé
        return
    if ar == None:              # Pas de forme sélectionnée
        return
    
    mvt = True                  # Un mouvement a commencé
    x, y = event.xdata, event.ydata     # Les coordonnées de la souris
    
    ar.deplacer(x-dx, y-dy)
    
    fig.canvas.draw()


# Ce qui se produit quand le bouton de la souris est relaché
def quandRelache(event):
    global ar, mvt
    if not mvt and ar is not None:
        ar.changer_couleur(couleur_aleatoire())
        fig.canvas.draw()
    ar = None
    mvt = False


# Connection du canvas avec des événements
fig.canvas.mpl_connect('motion_notify_event', quandMouvement)
fig.canvas.mpl_connect('pick_event', quandChoix)
fig.canvas.mpl_connect('button_release_event', quandRelache)

plt.show()

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *