Chapitre 10
La programmation orientée objet
Introduction
Pour traiter un problème, il existe deux approches principales :
- Approche procédurale : basée sur des fonctions indépendantes des données. Chaque fonction est associée à un traitement et peut être réutilisée dans différents contextes.
- Approche orientée objet (POO) : basée sur des objets qui regroupent données et comportements. Le code est découpé en entités autonomes appelées classes. Les données sont protégées par encapsulation, ce qui permet de mieux modéliser la réalité.
L'encapsulation regroupe les données (attributs) et les fonctions qui les manipulent (méthodes) dans une même entité : la classe. L'instance d'une classe est appelée objet.
- Une classe est équivalente à un nouveau type de données (comme
intoulist). - On peut créer autant d'objets (instances) que l'on souhaite à partir d'une même classe.
- Les classes Python existantes (
int,list,dict…) suivent ce même principe.
Classes et Objets
Pour créer une classe, on précise son nom, ses attributs (ce qui la décrit) et ses méthodes (ce qu'elle peut faire).
Exemple — Classe CompteBancaire :
- Attributs :
numCompte,solde,dteOuverture,typeCompte - Méthodes :
ouvrir(),fermer(),depot(),retrait()
- Attributs de classe : définis dans le corps de la classe, en dehors de toute méthode. Partagés par toutes les instances.
-
Attributs d'instance : définis dans le constructeur via
self.nom. Propres à chaque objet, créés et détruits avec lui.
Exemple — Classe Time1 avec attributs de classe :
class Time1:
"""Classe représentant une heure."""
h = 0 # attribut de classe (partagé par toutes les instances)
m = 0
s = 0
def modifierHeure(self, heure):
if 0 <= heure <= 23:
self.h = heure
else:
raise ValueError("L'heure doit être entre 0 et 23")
def afficheTime(self):
print(str(self.h) + ":" + str(self.m) + ":" + str(self.s))
t = Time1()
t.afficheTime() # 0:0:0
t.modifierHeure(21)
t.afficheTime() # 21:0:0
q = Time1()
q.afficheTime() # 0:0:0 (attributs de classe inchangés)
Exemple — Classe Time2 avec constructeur paramétré :
class Time2:
"""Classe représentant une heure (attributs d'instance)."""
def __init__(self, heure=0, minute=0, seconde=0):
self.h = heure # attribut d'instance
self.m = minute
self.s = seconde
def afficheTime(self):
print(str(self.h) + ":" + str(self.m) + ":" + str(self.s))
q = Time2(12, 25, 10)
q.afficheTime() # 12:25:10
# Time2.h → AttributeError : pas d'attribut de classe h
Les méthodes spéciales
Les méthodes spéciales (ou dunder methods) portent des noms prédéfinis entourés de doubles tirets bas. Elles permettent de personnaliser le comportement des objets avec les opérateurs Python.
__init__(self, ...)— Constructeur : initialise un nouvel objet.__repr__(self)— Représentation officielle, appelée parrepr()et dans la console.__str__(self)— Représentation lisible, appelée parprint()etstr().__add__(self, autre)— Surcharge de+: permet d'écrireobj1 + obj2.__sub__(self, autre)— Surcharge de-.__mul__(self, autre)— Surcharge de*.__truediv__(self, autre)— Surcharge de/.__eq__(self, autre)— Surcharge de==.__len__(self)— Surcharge delen().__getitem__(self, i)— Surcharge deobj[i].__call__(self, ...)— Surcharge de l'appelobj(...).
L'héritage
L'héritage permet de créer une sous-classe (classe dérivée) qui hérite des attributs et méthodes d'une classe parente (superclasse), et les étend ou les redéfinit.
- La classe fille hérite de tous les attributs et méthodes de la classe mère.
- On peut ajouter de nouvelles méthodes ou attributs.
- On peut redéfinir (surcharger) des méthodes héritées.
super()appelle le constructeur ou une méthode de la classe parente.- Syntaxe :
class SousClasse(ClasseMere):
Exemple — Géométrie : Polygone est la classe de base ;
Rectangle en hérite, et Carre hérite de Rectangle.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, p):
return ((self.x - p.x)**2 + (self.y - p.y)**2)**0.5
def __repr__(self):
return 'Point(' + str(self.x) + ',' + str(self.y) + ')'
def __str__(self):
return '(' + str(self.x) + ',' + str(self.y) + ')'
class Polygone:
def __init__(self, listPoints):
self.sommets = listPoints
self.nom = "Polygone"
def perimetre(self):
p = 0
for i in range(len(self.sommets) - 1):
p += self.sommets[i].distance(self.sommets[i + 1])
p += self.sommets[0].distance(self.sommets[-1])
return p
def __repr__(self):
return self.nom + ":" + "-->".join([str(s) for s in self.sommets])
class Rectangle(Polygone): # hérite de Polygone
def __init__(self, listPoints):
super().__init__(listPoints)
self.nom = "Rectangle"
def longueur(self):
return self.sommets[0].distance(self.sommets[1])
def largeur(self):
return self.sommets[1].distance(self.sommets[2])
class Carre(Rectangle): # hérite de Rectangle
def __init__(self, listPoints):
super().__init__(listPoints)
self.nom = "Carre"
# Utilisation
A = Point(0, 0); B = Point(4, 0); C = Point(4, 3); D = Point(0, 3)
r = Rectangle([A, B, C, D])
print(r) # Rectangle:(0,0)-->(4,0)-->(4,3)-->(0,3)
print(r.perimetre()) # 14.0
print(r.longueur()) # 4.0
Exercices avec corrigés
L'objectif est de créer une classe Python pour la manipulation des
nombres complexes. On l'appellera Cplx.
Le constructeur reçoit la partie réelle x et la partie imaginaire
y. Les attributs sont re et im.
Ainsi z = Cplx(1, -3) crée le complexe z = 1 − 3i.
Définir dans la classe Cplx les méthodes suivantes :
__init__(self, x, y)— constructeur.__repr__(self)— affiche le complexe sous la formea+bi.__add__(self, autre)— addition de deux complexes (z1 + z2).module(self)— retourne le module.conjugue(self)— retourne le conjugué.inverse(self)— retourne l'inverse.__sub__(self, autre)— soustraction (z1 - z2).__mul__(self, autre)— multiplication (z1 * z2).__truediv__(self, autre)— division (z1 / z2).__eq__(self, autre)— égalité (z1 == z2).
Construire la classe Poly pour créer et manipuler des
polynômes à coefficients réels. Les attributs seront :
self.c: liste des coefficients non nuls.self.d: liste des degrés associés (dans l'ordre décroissant).
Exemple : P = Poly([-5, 2, 1], [4, 3, 0]) représente −5x⁴ + 2x³ + 1.
Redéfinir : __init__, __repr__, __add__,
__sub__, __eq__.
Définir :
eval(x)— valeur du polynôme pour un réel x.deriv()— polynôme dérivé.integrale(a, b)— intégrale entre a et b.
Concevoir une classe Rationnel pour manipuler des
nombres rationnels. Les rationnels calculés doivent toujours être
des fractions irréductibles.
__init__(self, n=0, d=1)— crée le rationnel n/d.setNumer(n),setDenom(d)— modifient les valeurs.__repr__(self)— affiche sous forme de fraction (ex :3/4).__add__,__mul__,__truediv__,__eq__.inv()— inverse du rationnel.pgcd(a, b)— PGCD de deux entiers.irreductible()— simplifie la fraction.
Un polynôme creux est un polynôme dont certains coefficients sont nuls.
Un monôme axn est représenté par un dictionnaire {n: a}.
Un polynôme creux est une association de monômes stockée dans l'attribut data.
Compléter le squelette de la classe PolynomeCreux :
- Q1 —
ajout_monome(monome={}): ajoute un monôme saisi au clavier simonomeest vide, sinon ajoute le monôme passé en argument. - Q2 —
degree(): retourne le degré du polynôme. - Q3 —
__call__(self, x0): valeur du polynôme pourx0. - Q4 —
__add__(self, other): somme (sans monômes nuls). - Q5 —
__mul__(self, other): produit (sans monômes nuls). - Q6 —
__str__(self): chaîne ordonnée par degrés décroissants. Ex :"6*x**12 + x**9 - x**7 + 4". - Q7 —
primitive(): primitive du polynôme (constante nulle). - Q8 —
integrale(a, b): valeur de l'intégrale entre a et b.
class PolynomeCreux:
"""Manipulation des polynômes creux à une seule variable."""
def __init__(self):
self.data = {} # polynôme nul
def ajout_monome(self, monome={}):
if len(monome) == 0:
# Q1 : saisir degré et coefficient au clavier
...
else:
degre = list(monome.keys())[0]
self.data[degre] = list(monome.values())[0]
def degree(self): ... # Q2
def __call__(self, x0): ... # Q3
def __add__(self, other): ... # Q4
def __mul__(self, other): ... # Q5
def __str__(self): ... # Q6
def primitive(self): ... # Q7
def integrale(self, a, b): ... # Q8
Classe Point : attributs x, y.
distance(point)— distance entre deux points.translate(dx, dy)— translate le point.isobarycentre(points)— isobarycentre d'un ensemble de points.- Redéfinir
__repr__et__eq__.
Classe Segment : attributs point1, point2.
longueur()— déléguer le calcul àPoint.distance().milieu()— retourne le point milieu.- Redéfinir
__repr__et__eq__.
Classe Cercle : attributs centre (Point) et rayon.
- Redéfinir
__repr__→ "Cercle de centre (x,y) et de rayon r". getPerimetre(),getSurface().appartient(point),interieur(point).
Classe Rectangle : attributs coin_haut_gauche et coin_bas_droite (Points).
getPerimetre()etgetSurface().
Écrire la classe VectorOfInt qui gère un tableau à
capacité dynamique. Elle contient un tableau d'entiers
TE et un entier size. Fournir les méthodes :
ensureCapacity(c)— si capacité < c, nouvelle capacité = max(c, 2 × capacité actuelle). Nouvelles cases à 0.resize(s)— modifie la taille, augmente la capacité si nécessaire.getSize()— taille actuelle.isEmpty()—Truesi vide.add(e)— ajoute un élément à la fin.set(i, e)— modifie l'élément à la position i.get(i)— retourne l'élément à la position i (Nonesi hors bornes).sum(),max(),indexMax(),sort().
Écrire la classe StackOfInt qui gère une pile d'entiers
en s'appuyant sur un objet de type VectorOfInt. Fournir :
push(i)— empile un entier.peek()— retourne l'entier en sommet sans le dépiler.pop()— dépile et retourne l'entier en sommet.size()— nombre d'entiers dans la pile.isEmpty()—Truesi la pile est vide.
from math import sqrt
class Cplx:
def __init__(self, x, y):
self.re = x
self.im = y
def __repr__(self):
return str(self.re) + '+' + str(self.im) + 'i'
def __add__(self, autre):
return Cplx(self.re + autre.re, self.im + autre.im)
def module(self):
return sqrt(self.re**2 + self.im**2)
def conjugue(self):
return Cplx(self.re, -self.im)
def inverse(self):
m2 = self.module()**2
return Cplx(self.re / m2, -self.im / m2)
def __sub__(self, autre):
return Cplx(self.re - autre.re, self.im - autre.im)
def __mul__(self, autre):
re = self.re * autre.re - self.im * autre.im
im = self.re * autre.im + self.im * autre.re
return Cplx(re, im)
def __truediv__(self, autre):
return self * autre.inverse()
def __eq__(self, autre):
return self.re == autre.re and self.im == autre.im
# Exemples
z1 = Cplx(1, 2)
z2 = Cplx(3, -4)
print(z1) # 1+2i
print(z1 + z2) # 4+-2i
print(z1 * z2) # 11+2i
print(z1.module()) # 2.2360679...
print(z1 == z2) # False
print(z1 / z2) # appelle z1 * z2.inverse()
class Poly:
def __init__(self, coeff, degre):
self.c = coeff # coefficients non nuls
self.d = degre # degrés associés (ordre décroissant)
def __repr__(self):
termes = [str(self.c[i]) + 'x^' + str(self.d[i])
for i in range(len(self.c))]
return ' + '.join(termes)
def __add__(self, autre):
i, j = 0, 0
Lc, Ld = [], []
while i < len(self.d) and j < len(autre.d):
if self.d[i] > autre.d[j]:
Ld.append(self.d[i]); Lc.append(self.c[i]); i += 1
elif self.d[i] < autre.d[j]:
Ld.append(autre.d[j]); Lc.append(autre.c[j]); j += 1
else:
s = self.c[i] + autre.c[j]
if s != 0:
Ld.append(self.d[i]); Lc.append(s)
i += 1; j += 1
while i < len(self.d):
Ld.append(self.d[i]); Lc.append(self.c[i]); i += 1
while j < len(autre.d):
Ld.append(autre.d[j]); Lc.append(autre.c[j]); j += 1
return Poly(Lc, Ld)
def __sub__(self, autre):
neg = Poly([-x for x in autre.c], autre.d[:])
return self + neg
def __eq__(self, autre):
return self.c == autre.c and self.d == autre.d
def eval(self, x):
return sum(self.c[i] * x**self.d[i] for i in range(len(self.c)))
def deriv(self):
Lc = [self.c[i] * self.d[i] for i in range(len(self.c)) if self.d[i] != 0]
Ld = [self.d[i] - 1 for i in range(len(self.d)) if self.d[i] != 0]
return Poly(Lc, Ld)
def integrale(self, a, b):
prim_c = [self.c[i] / (self.d[i] + 1) for i in range(len(self.c))]
prim_d = [self.d[i] + 1 for i in range(len(self.d))]
P = Poly(prim_c, prim_d)
return P.eval(b) - P.eval(a)
# Exemple
P = Poly([-5, 2, 1], [4, 3, 0])
print(P) # -5x^4 + 2x^3 + 1
print(P.eval(1)) # -2
print(P.deriv()) # -20x^3 + 6x^2
class Rationnel:
def __init__(self, n=0, d=1):
self.numer = n
self.denom = d
self.irreductible()
def pgcd(self, a, b):
while b != 0:
a, b = b, a % b
return a
def irreductible(self):
if self.numer == 0:
return
p = self.pgcd(abs(self.numer), abs(self.denom))
self.numer //= p
self.denom //= p
def setNumer(self, n):
self.numer = n
self.irreductible()
def setDenom(self, d):
self.denom = d
self.irreductible()
def __repr__(self):
if self.numer == 0:
return "0"
if self.denom == 1:
return str(self.numer)
return str(self.numer) + "/" + str(self.denom)
def __add__(self, autre):
n = self.numer * autre.denom + self.denom * autre.numer
return Rationnel(n, self.denom * autre.denom)
def __mul__(self, autre):
return Rationnel(self.numer * autre.numer, self.denom * autre.denom)
def __truediv__(self, autre):
return self * autre.inv()
def __eq__(self, autre):
return self.numer == autre.numer and self.denom == autre.denom
def inv(self):
return Rationnel(self.denom, self.numer)
# Utilisation
r1 = Rationnel(1, 2)
r2 = Rationnel(1, 3)
print(r1) # 1/2
print(r1 + r2) # 5/6
print(r1 * r2) # 1/6
print(r1 / r2) # 3/2
class PolynomeCreux:
"""Manipulation des polynômes creux à une seule variable."""
def __init__(self):
self.data = {}
def ajout_monome(self, monome={}):
if len(monome) == 0:
# Q1 : saisie au clavier avec validations
while True:
n = int(input("degré = "))
if n >= 0:
break
while True:
a = float(input("coefficient = "))
if a != 0:
break
self.data[n] = a
else:
n = list(monome.keys())[0]
self.data[n] = list(monome.values())[0]
def degree(self):
# Q2
return max(self.data.keys())
def __call__(self, x0):
# Q3
return sum(self.data[c] * x0**c for c in self.data)
def __add__(self, other):
# Q4
p = PolynomeCreux()
all_deg = set(self.data.keys()) | set(other.data.keys())
for d in all_deg:
c = self.data.get(d, 0) + other.data.get(d, 0)
if c != 0:
p.ajout_monome({d: c})
return p
def __mul__(self, other):
# Q5
p = PolynomeCreux()
for d1 in self.data:
for d2 in other.data:
d = d1 + d2
c = self.data[d1] * other.data[d2]
if d in p.data:
p.data[d] += c
else:
p.data[d] = c
p.data = {d: c for d, c in p.data.items() if c != 0}
return p
def __str__(self):
# Q6
if not self.data:
return "0"
L = sorted(self.data.keys(), reverse=True)
termes = []
for d in L:
c = self.data[d]
if d == 0:
termes.append(str(c))
elif d == 1:
termes.append(str(c) + '*x')
else:
termes.append(str(c) + '*x**' + str(d))
return ' + '.join(termes)
def primitive(self):
# Q7
p = PolynomeCreux()
for d in self.data:
p.ajout_monome({d + 1: self.data[d] / (d + 1)})
return p
def integrale(self, a, b):
# Q8
return self.primitive()(b) - self.primitive()(a)
# Exemple
P = PolynomeCreux()
P.ajout_monome({2: 3})
P.ajout_monome({0: -1})
print(P) # 3*x**2 + -1
print(P(2)) # 11.0
print(P.integrale(0, 1)) # 2.0
from math import pi, sqrt
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, point):
return sqrt((self.x - point.x)**2 + (self.y - point.y)**2)
def translate(self, dx, dy):
self.x += dx
self.y += dy
def isobarycentre(self, points):
tous = [self] + points
mx = sum(p.x for p in tous) / len(tous)
my = sum(p.y for p in tous) / len(tous)
return Point(mx, my)
def __repr__(self):
return 'Point(' + str(self.x) + ', ' + str(self.y) + ')'
def __eq__(self, autre):
return self.x == autre.x and self.y == autre.y
class Segment:
def __init__(self, point1, point2):
self.point1 = point1
self.point2 = point2
def longueur(self):
return self.point1.distance(self.point2)
def milieu(self):
mx = (self.point1.x + self.point2.x) / 2
my = (self.point1.y + self.point2.y) / 2
return Point(mx, my)
def __repr__(self):
return 'Segment(' + repr(self.point1) + ', ' + repr(self.point2) + ')'
def __eq__(self, autre):
return ((self.point1 == autre.point1 and self.point2 == autre.point2) or
(self.point1 == autre.point2 and self.point2 == autre.point1))
class Cercle:
def __init__(self, centre, rayon):
self.centre = centre
self.rayon = rayon
def __repr__(self):
return ('Cercle de centre (' + str(self.centre.x) + ',' +
str(self.centre.y) + ') et de rayon ' + str(self.rayon))
def getPerimetre(self):
return 2 * pi * self.rayon
def getSurface(self):
return pi * self.rayon**2
def appartient(self, point):
return self.centre.distance(point) == self.rayon
def interieur(self, point):
return self.centre.distance(point) < self.rayon
class Rectangle:
def __init__(self, coin_haut_gauche, coin_bas_droite):
self.chg = coin_haut_gauche
self.cbd = coin_bas_droite
def getPerimetre(self):
l = abs(self.cbd.x - self.chg.x)
h = abs(self.cbd.y - self.chg.y)
return 2 * (l + h)
def getSurface(self):
l = abs(self.cbd.x - self.chg.x)
h = abs(self.cbd.y - self.chg.y)
return l * h
# Exemples
A = Point(0, 0); B = Point(3, 4)
print(A.distance(B)) # 5.0
s = Segment(A, B)
print(s.longueur()) # 5.0
print(s.milieu()) # Point(1.5, 2.0)
c = Cercle(Point(0, 0), 5)
print(c) # Cercle de centre (0,0) et de rayon 5
r = Rectangle(Point(0, 4), Point(3, 0))
print(r.getPerimetre()) # 14
print(r.getSurface()) # 12
class VectorOfInt:
def __init__(self, capacite=0):
self.TE = [0] * capacite
self.size = 0
def ensureCapacity(self, c):
if len(self.TE) < c:
m = max(c, 2 * len(self.TE)) if len(self.TE) > 0 else c
self.TE = self.TE + [0] * (m - len(self.TE))
def __repr__(self):
return ', '.join(str(self.TE[i]) for i in range(self.size))
def resize(self, s):
if len(self.TE) < s:
self.ensureCapacity(s)
self.size = s
def getSize(self):
return self.size
def isEmpty(self):
return self.size == 0
def add(self, e):
self.size += 1
self.ensureCapacity(self.size)
self.TE[self.size - 1] = e
def set(self, i, e):
if i < self.size:
self.TE[i] = e
def get(self, i):
if i < self.size:
return self.TE[i]
return None
def __getitem__(self, i):
return self.get(i)
def sum(self):
return sum(self.TE[:self.size])
def max(self):
return max(self.TE[:self.size])
def indexMax(self):
return self.TE[:self.size].index(self.max())
def sort(self):
A = self.TE[:self.size]
A.sort()
self.TE[:self.size] = A
# Utilisation
v = VectorOfInt()
v.add(5); v.add(2); v.add(8); v.add(1)
print(v) # 5, 2, 8, 1
print(v.max()) # 8
print(v.indexMax()) # 2
v.sort()
print(v) # 1, 2, 5, 8
class StackOfInt:
def __init__(self):
self.v = VectorOfInt()
def push(self, i):
self.v.add(i)
def peek(self):
if self.v.getSize() == 0:
return None
return self.v[self.v.getSize() - 1]
def pop(self):
x = self.peek()
if x is not None:
self.v.resize(self.v.getSize() - 1)
return x
def size(self):
return self.v.getSize()
def isEmpty(self):
return self.v.isEmpty()
# Utilisation
stack = StackOfInt()
stack.push(10)
stack.push(20)
stack.push(30)
print(stack.peek()) # 30
print(stack.pop()) # 30
print(stack.size()) # 2