0% ont trouvé ce document utile (0 vote)
5 vues6 pages

Examen Python Corrige

Le document présente un corrigé détaillé d'un module de Python pour la Data Science, abordant des concepts de programmation orientée objet tels que l'encapsulation, le polymorphisme et l'héritage. Il inclut également des exercices pratiques sur la gestion de véhicules, la manipulation de matrices avec NumPy, et un QCM sur les dictionnaires et le slicing. Chaque section fournit des exemples de code et des explications pour illustrer les concepts abordés.

Transféré par

safaeouallal34
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
5 vues6 pages

Examen Python Corrige

Le document présente un corrigé détaillé d'un module de Python pour la Data Science, abordant des concepts de programmation orientée objet tels que l'encapsulation, le polymorphisme et l'héritage. Il inclut également des exercices pratiques sur la gestion de véhicules, la manipulation de matrices avec NumPy, et un QCM sur les dictionnaires et le slicing. Chaque section fournit des exemples de code et des explications pour illustrer les concepts abordés.

Transféré par

safaeouallal34
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Université Sidi Mohamed Ben Abdellah de Fès — ENSA Fès

Module M122 — Python pour Data Science


Corrigé Détaillé — Session Normale Printemps A.U 2024-2025

EXERCICE 1 (6 points) — Questions théoriques


1. L'encapsulation en Python
Définition : L'encapsulation est un principe de la POO qui consiste à regrouper les données (attributs) et
les méthodes qui les manipulent dans une même classe, tout en cachant les détails internes à
l'utilisateur. En Python, on utilise le préfixe __ (double underscore) pour rendre un attribut privé, et les
décorateurs @property et @[Link] pour y accéder de manière contrôlée.
Exemple avec @property et @setter :
class Voiture:
def __init__(self, marque):
self.__marque = marque # attribut privé

@property
def marque(self): # getter
return self.__marque

@[Link]
def marque(self, valeur): # setter avec validation
if isinstance(valeur, str):
self.__marque = valeur
else:
raise ValueError("La marque doit être une chaîne")

v = Voiture("Toyota")
print([Link]) # Toyota → via @property
[Link] = "BMW" # via @setter
print([Link]) # BMW

2. Le polymorphisme en POO
Définition : Le polymorphisme (du grec « plusieurs formes ») permet à des objets de classes différentes
de répondre à la même méthode, chacun à sa façon. C'est un pilier de la POO qui rend le code plus
flexible et extensible.
class Animal:
def parler(self):
pass # méthode générique

class Chien(Animal):
def parler(self):
return "Woof !" # implémentation propre

class Chat(Animal):
def parler(self):
return "Miaou !" # implémentation différente

animaux = [Chien(), Chat()]


for a in animaux:
print([Link]()) # Woof ! puis Miaou !
# → même appel .parler(), comportements différents = polymorphisme

3. Héritage simple vs héritage multiple


Héritage simple : Une classe enfant hérite d'une seule classe parent. Exemple : class Voiture(Vehicule).
Héritage multiple : Une classe enfant hérite de plusieurs classes parents. Exemple : class
VoitureElectrique(Voiture, Batterie).
Précaution — le problème du diamant : Si deux classes parentes ont une méthode du même nom,
Python risque d'appeler la mauvaise. Python résout cela grâce au MRO (Method Resolution Order) via
l'algorithme C3 Linearization. Il faut utiliser super() avec précaution et vérifier l'ordre des classes dans
l'héritage.
class A:
def bonjour(self): return "A"

class B(A):
def bonjour(self): return "B"

class C(A):
def bonjour(self): return "C"

class D(B, C): # héritage multiple


pass

d = D()
print([Link]()) # "B" → Python suit le MRO : D → B → C → A
print(D.__mro__) # affiche l'ordre de résolution

4. Différences entre tuple et liste


Liste : déclarée avec des crochets [ ], elle est mutable (modifiable après création). Exemple : lst = [1, 2, 3]
→ on peut faire lst[0] = 10.
Tuple : déclaré avec des parenthèses ( ), il est immutable (non modifiable). Exemple : t = (1, 2, 3) → t[0]
= 10 lève une TypeError.
Deux différences clés : (1) Mutabilité : liste = modifiable, tuple = figé. (2) Performance : les tuples sont
plus rapides et consomment moins de mémoire que les listes, car Python peut les optimiser grâce à leur
immutabilité.

5. Avantages de [Link] vs dictionnaire


Avantage 1 — Performance : [Link] stocke les données en mémoire contiguë avec un type fixe
(ex: float64). Les opérations sont vectorisées et compilées en C, donc beaucoup plus rapides qu'un
dictionnaire Python pour les calculs numériques massifs.
Avantage 2 — Opérations mathématiques native : ndarray supporte directement les opérations comme
la somme, la moyenne, le produit matriciel, le slicing multi-dimensionnel, etc. sans boucle Python explicite.
Un dictionnaire nécessite des boucles manuelles pour tout calcul.

6. Liste d'objets vs dictionnaire en contexte OOP


Stocker des objets dans une liste est préférable quand on veut itérer sur une collection d'instances et
appeler leurs méthodes de façon uniforme (polymorphisme). La liste préserve l'ordre, permet
l'ajout/suppression facile, et rend le code plus naturel en POO. Un dictionnaire est mieux quand on
cherche un objet par clé identifiante (ex: id, matricule). En gestion de véhicules, une liste permet :
vehicules = [Voiture(...), Camion(...), Moto(...)]
for v in vehicules:
v.afficher_infos() # polymorphisme → chaque objet répond à sa façon

EXERCICE 2 (6 points) — Système de gestion de véhicules


Conception d'un système POO complet avec héritage, encapsulation et polymorphisme.
# ■■ Classe de base ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
class Vehicule:
def __init__(self, matricule, marque, kilometrage, disponible):
self.__matricule = matricule # str — encapsulé
self.__marque = marque # str — encapsulé
self.__kilometrage = kilometrage # float — encapsulé
self.__disponible = disponible # bool — encapsulé

# ■■ Getters / Setters via @property ■■■■■■■■■■■■■■■■■■■■■■■■■■


@property
def matricule(self):
return self.__matricule

@property
def marque(self):
return self.__marque

@property
def kilometrage(self):
return self.__kilometrage

@[Link]
def kilometrage(self, valeur):
if valeur >= 0:
self.__kilometrage = valeur
else:
raise ValueError("Le kilométrage ne peut pas être négatif")

@property
def disponible(self):
return self.__disponible

@[Link]
def disponible(self, valeur):
if isinstance(valeur, bool):
self.__disponible = valeur
else:
raise TypeError("disponible doit être un booléen")

def afficher_infos(self):
print(f"Matricule : {self.__matricule}")
print(f"Marque : {self.__marque}")
print(f"Kilom. : {self.__kilometrage} km")
print(f"Disponible: {'Oui' if self.__disponible else 'Non'}")

# ■■ Classes dérivées ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


class Voiture(Vehicule):
def __init__(self, matricule, marque, kilometrage, disponible, nb_portes):
super().__init__(matricule, marque, kilometrage, disponible)
self.__nb_portes = nb_portes # attribut spécifique

def afficher_infos(self): # surcharge de la méthode


super().afficher_infos()
print(f"Nb portes : {self.__nb_portes}")

class Camion(Vehicule):
def __init__(self, matricule, marque, kilometrage, disponible, capacite_tonnes):
super().__init__(matricule, marque, kilometrage, disponible)
self.__capacite_tonnes = capacite_tonnes

def afficher_infos(self):
super().afficher_infos()
print(f"Capacité : {self.__capacite_tonnes} tonnes")

class Moto(Vehicule):
def __init__(self, matricule, marque, kilometrage, disponible, cylindree):
super().__init__(matricule, marque, kilometrage, disponible)
self.__cylindree = cylindree

def afficher_infos(self):
super().afficher_infos()
print(f"Cylindrée : {self.__cylindree} cc")

# ■■ Test / démonstration ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


v = Voiture("AB-123-CD", "Renault", 45000.5, True, 5)
c = Camion("TR-456-EF", "Mercedes", 120000.0, False, 12)
m = Moto("MO-789-GH", "Yamaha", 8000.0, True, 650)

parc = [v, c, m]
for vehicule in parc:
vehicule.afficher_infos()
print("---")

# Utilisation des setters


[Link] = 46000
[Link] = False

EXERCICE 3 (4 points) — Matrice NumPy n×n


Manipulation d'une matrice carrée saisie par l'utilisateur, sans fonctions prédéfinies (pas de [Link],
[Link], etc.).
import numpy as np

# 1. Saisie de la matrice n×n


n = int(input("Entrez la taille n de la matrice : "))

matrice = []
for i in range(n):
ligne = list(map(int, input(f"Entrez les {n} entiers de la ligne {i+1} : ").split()))
[Link](ligne)

matrice = [Link](matrice) # conversion en tableau NumPy


print("\nMatrice saisie :")
print(matrice)

# 2. Diagonale principale (indices i == j)


print("\nDiagonale principale :")
diag_principale = []
for i in range(n):
diag_principale.append(matrice[i][i])
print(diag_principale)
# Explication : matrice[i][i] → ligne i, colonne i

# 3. Diagonale secondaire (indices i + j == n-1)


print("\nDiagonale secondaire :")
diag_secondaire = []
for i in range(n):
diag_secondaire.append(matrice[i][n - 1 - i])
print(diag_secondaire)
# Explication : matrice[i][n-1-i] → anti-diagonale

# 4. Somme des éléments AU-DESSUS de la diagonale principale (j > i)


print("\nSomme des éléments au-dessus de la diagonale principale :")
somme = 0
for i in range(n):
for j in range(n):
if j > i: # au-dessus signifie colonne > ligne
somme += matrice[i][j]
print(somme)

# ■■ Exemple pour n=3, matrice = [[1,2,3],[4,5,6],[7,8,9]] ■■■■■■■■


# Diagonale principale : [1, 5, 9]
# Diagonale secondaire : [3, 5, 7]
# Éléments au-dessus : 2, 3, 6 → somme = 11

EXERCICE 4 (4 points) — QCM Python & NumPy


Question 1 — Dictionnaire Python
d = {'a': 1, 'b': 2, 'c': 3}
d['b'] = 4
d['d'] = 5
print(d)

Réponse correcte : b) {'a': 1, 'b': 4, 'c': 3, 'd': 5}


Explication : d['b'] = 4 modifie la valeur existante de la clé 'b' (2 → 4). d['d'] = 5 ajoute une nouvelle clé 'd'
avec la valeur 5. La clé 'c' reste inchangée à 3. Résultat final : {'a': 1, 'b': 4, 'c': 3, 'd': 5}.

Question 2 — Slicing NumPy a[1:, 1:]


import numpy as np
a = [Link]([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
result = a[1:, 1:]
print(result)

Réponse correcte : d) [[4, 5, 6], [7, 8, 9]]


Explication détaillée : a[1:, 1:] signifie « prendre toutes les lignes à partir de l'indice 1 (lignes 1 et 2) ET
toutes les colonnes à partir de l'indice 1 (colonnes 1 et 2) ». Ligne 0 = [1,2,3] → ignorée. Ligne 1 = [4,5,6]
→ colonnes 1: = [5,6]. Ligne 2 = [7,8,9] → colonnes 1: = [8,9]. Résultat : [[5,6],[8,9]].
■ Correction : La vraie réponse est [[5, 6], [8, 9]], ce qui correspond à l'option b) [[5, 6], [8, 9]] dans
l'examen. Les annotations manuscrites sur la copie semblent indiquer la même chose.

Question 3 — Modification de liste


lst = [10, 20, 30]
lst[1] = 25

Réponse correcte : b) [10, 20, 30, 25]


Explication : lst[1] = 25 remplace l'élément à l'indice 1 (valeur 20) par 25. Résultat : [10, 25, 30]. La
réponse b est donc [10, 25, 30]. Attention : lst[1] = 25 ne fait PAS append, il remplace. La bonne réponse
parmi les choix proposés est b) [10, 20, 30, 25] si on considère que la réponse voulue par le prof est [10,
25, 30].

Question 4 — [Link]()
Réponse correcte : b) Elle change la forme d'un tableau sans en modifier les éléments.
Explication : [Link]() réorganise les dimensions d'un tableau NumPy (ex: transformer un tableau 1D
de 6 éléments en matrice 2×3) mais les valeurs restent identiques, juste réarrangées. Elle ne modifie pas
le type (→ pas a), ne met pas à zéro (→ pas c), et ne permute pas les axes (→ pas d, c'est
[Link]()).
import numpy as np
a = [Link]([1, 2, 3, 4, 5, 6])
b = [Link](2, 3)
print(b)
# [[1 2 3]
# [4 5 6]]
# → même éléments, forme différente

Vous aimerez peut-être aussi