Une utilisation sérieuse

Python s’Objette aux Fractions : Un Voyage dans le Monde des Fractions avec Python

Imaginez que vous travaillez pour une entreprise de développement de logiciels éducatifs. Votre dernière mission est de développer un module pour aider les élèves à comprendre les fractions. Pour cela, vous avez décidé d’utiliser Python et la programmation orientée objet. Votre objectif est de créer une classe Fraction qui représente une fraction avec un numérateur et un dénominateur. Cette classe doit permettre aux utilisateurs de faire des opérations arithmétiques avec des fractions, comme l’addition, la soustraction, la multiplication et la division, et doit permettre la comparaison des fractions entre-elles. De plus, elle doit garantir que les fractions sont toujours simplifiées à leur forme la plus simple. Alors, prêt à relever le défi ?

N'oubliez pas de tester vos fonctions ! Cet exercice propose une implémentation des fractions en Python, mais vous pouvez ne pas suivre l'énoncé et proposer votre propre version.

Question

Classes et attributs :

Commencez par définir une classe Fraction. Cette classe doit avoir deux attributs : num (numérateur) et denom (dénominateur). Vérifiez que les propriétés nécessaires à nos fractions soient respectées à l'aide de assert.

Indice

Vous pouvez initialiser ces attributs dans la méthode __init__. Assurez-vous que le dénominateur ne soit jamais égal à zéro et que le numérateur et le dénomiateur soient de type int en ajoutant une condition dans la méthode __init__.

Solution

1
class Fraction:
2
    def __init__(self, num, denom):
3
        assert isinstance(num, int) and isinstance(denom, int), "Les paramètres fournis doivent être des entiers"
4
        assert denom != 0, "Le dénominateur ne peut pas être zéro."
5
        self.num = num
6
        self.denom = denom

Question

Méthodes :

Ajoutez une méthode simplifier à la classe. Cette méthode doit simplifier la fraction en réduisant le numérateur et le dénominateur à leur plus petit commun diviseur.

Ajoutez ensuite une méthode __str__ pour pouvoir afficher convenablement les fractions sur une seule ligne.

Indice

Vous pouvez utiliser l’algorithme d’Euclide pour trouver le plus grand commun diviseur. Consultez l’article Wikipédia sur l’algorithme d’Euclide pour plus de détails.

Solution

1
def pgcd(a, b):
2
    # L'algorithme d'Euclide pour trouver le plus grand commun diviseur (PGCD) fonctionne comme suit :
3
    # 1. Si b est 0, alors le PGCD est a
4
    # 2. Sinon, on calcule le PGCD de b et du reste de la division euclidienne de a par b
5
    # Cette procédure est répétée jusqu'à ce que b soit égal à zéro
6
7
    while b != 0:
8
        # On échange les valeurs de a et b, et on remplace b par le reste de la division euclidienne de a par b
9
        a, b = b, a % b
10
    # Quand la boucle se termine (c'est-à-dire quand b est égal à zéro), a contient le PGCD
11
    return a
12
13
class Fraction:
14
    # ...
15
    def simplifier(self):
16
        # Trouve le PGCD du numérateur et du dénominateur
17
        gcd = pgcd(self.num, self.denom)
18
        # Divise le numérateur et le dénominateur par leur PGCD
19
        self.num /= gcd
20
        self.denom /= gcd
21
22
    def __str__(self):
23
        return f"{self.num} / {self.denom}"

Question

Surcharge d’opérateurs :

Vous devez maintenant surcharger les opérateurs +, -, *, /. Ces méthodes doivent retourner une nouvelle instance de Fraction qui est le résultat de l’opération. N’oubliez pas de simplifier la fraction avant de retourner le résultat.

Rajoutez ensuite la surcharge du = et de la comparaison.

Indice

Pour cela, vous devez définir les méthodes spéciales __add__, __sub__, __mul__ ... qui prennent en argument une autre instance de Fraction. Consultez la documentation Python sur les méthodes spéciales pour plus d’informations.

Pour la surcharge du = et de la comparaison, vous devez utiliser les opérateurs présents sur la documentation Python (de __lt__ à __ge__)

Solution

1
class Fraction:
2
    # ...
3
    def __add__(self, other):
4
        # Calcule le numérateur et le dénominateur de la somme
5
        num = self.num * other.denom + other.num * self.denom
6
        denom = self.denom * other.denom
7
        # Crée une nouvelle fraction avec la somme
8
        result = Fraction(num, denom)
9
        # Simplifie la fraction avant de la retourner
10
        result.simplifier()
11
        return result
12
13
    # De même pour __sub__, __mul__ et __div__
14
15
    def __eq__(self, other):
16
        # Deux fractions sont égales si leurs numérateurs et dénominateurs sont respectivement égaux
17
        return self.num == other.num and self.denom == other.denom
18
19
    # De même pour __ne__
20
21
    def __lt__(self, other):
22
        # Une fraction est inférieure à une autre si le produit de son numérateur et du dénominateur de l'autre est inférieur au produit de son dénominateur et du numérateur de l'autre
23
        return self.num * other.denom < self.denom * other.num
24
25
    def __le__(self, other):
26
        # Une fraction est inférieure ou égale à une autre si elle est inférieure ou égale à l'autre
27
        return self < other or self == other
28
29
    # De même pour __gt__ et __ge__
30

Question

Encapsulation :

Pour assurer l’encapsulation, vous devez rendre protégés les attributs num et denom. Ensuite, vous devez fournir des méthodes d’accès (getters). Ces méthodes sont généralement appelées get_num, get_denom.

Indice

Pour assurer l’encapsulation, vous devez rendre les attributs protégés en les préfixant par un underscore (_).

Solution

1
def pgcd(a, b):
2
    # L'algorithme d'Euclide pour trouver le plus grand commun diviseur (PGCD) fonctionne comme suit :
3
    # 1. Si b est 0, alors le PGCD est a
4
    # 2. Sinon, on calcule le PGCD de b et du reste de la division euclidienne de a par b
5
    # Cette procédure est répétée jusqu'à ce que b soit égal à zéro
6
7
    while b != 0:
8
        # On échange les valeurs de a et b, et on remplace b par le reste de la division euclidienne de a par b
9
        a, b = b, a % b
10
    # Quand la boucle se termine (c'est-à-dire quand b est égal à zéro), a contient le PGCD
11
    return a
12
13
class Fraction:
14
    def __init__(self, num, denom):
15
        assert isinstance(num, int) and isinstance(denom, int), "Les paramètres fournis doivent être des entiers"
16
        assert denom != 0, "Le dénominateur ne peut pas être zéro."
17
        self._num = num
18
        self._denom = denom
19
20
    def simplifier(self):
21
        # Trouve le PGCD du numérateur et du dénominateur
22
        gcd = pgcd(self._num, self._denom)
23
        # Divise le numérateur et le dénominateur par leur PGCD
24
        self._num /= gcd
25
        self._denom /= gcd
26
27
    def __str__(self):
28
        return f"{self._num} / {self._denom}"
29
30
    def __add__(self, other):
31
        # Calcule le numérateur et le dénominateur de la somme
32
        num = self._num * other._denom + other._num * self._denom
33
        denom = self._denom * other._denom
34
        # Crée une nouvelle fraction avec la somme
35
        result = Fraction(num, denom)
36
        # Simplifie la fraction avant de la retourner
37
        result.simplifier()
38
        return result
39
40
    # De même pour __sub__, __mul__ et __div__
41
42
    def __eq__(self, other):
43
        # Deux fractions sont égales si leurs numérateurs et dénominateurs sont respectivement égaux
44
        return self._num == other._num and self._denom == other._denom
45
46
    # De même pour __ne__
47
48
    def __lt__(self, other):
49
        # Une fraction est inférieure à une autre si le produit de son numérateur et du dénominateur de l'autre est inférieur au produit de son dénominateur et du numérateur de l'autre
50
        return self._num * other._denom < self._denom * other._num
51
52
    def __le__(self, other):
53
        # Une fraction est inférieure ou égale à une autre si elle est inférieure ou égale à l'autre
54
        return self < other or self == other
55
56
    # De même pour __gt__ et __ge__
57
58
    def get_num(self):
59
        return self._num
60
61
    def get_denom(self):
62
        return self._denom