Rechercher
 

Exemple de définition d'une classe en Python

Python est un langage de programmation orienté objet. Il est donc possible de définir des nouveaux types de données par le biais de classes. Pour de plus amples informations sur la programmation orientée objet en Python, je vous renvoie vers les tuto associés.

Exemple de code

L'exemple ci-dessous défini un nouveau type de données de manipulation de nombres rationnels (de fractions). On peut par exemple imaginer le rationnel 3/5 qui est constitué d'un numérateur, fixé à la valeur 3, et d'un dénominateur, qui lui est fixé à la valeur 5. Pour rappel, mathématiquement parlant, il est interdit de mettre un dénominateur à 0. L'objectif principal de la classe est de vérifier les changements d'états sur vos nombres rationnels afin de garantir qu'un objet ne soit jamais mis dans un état incohérent. Au pire, on déclenche une exception (une erreur). Pour réaliser ces contrôles, la classe utilise le concept de propriétés (repérable dans le code via les décorateurs Python, introduit par le caractère @).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
 100 
 101 
 102 
 103 
 104 
 105 
 106 
 107 
 108 
 109 
 110 
 111 
 112 
 113 
 114 
 115 
#!/usr/bin/python3
# -*- coding: utf-8 -*-

class RationalException(Exception):
    
    def __init__(self, message):
        Exception.__init__(self, message)


class Rational(object):    

    def __init__(self, num=0, den=1):
        self.numerator = num
        self.denominator = den
        self.simplify()        
    
    @property
    def numerator(self):
        return self.__numerator
     
    @numerator.setter 
    def numerator(self, newNum):
        if isinstance(newNum, int) == False:
            raise RationalException("Numerator must be an integer")
        self.__numerator = newNum
         
         
    @property
    def denominator(self):
        return self.__denominator
    
    @denominator.setter 
    def denominator(self, newDen):
        if isinstance( newDen, int ) == False:
            raise RationalException("Denominator must be an integer")
        if newDen == 0:
            raise RationalException("Denominator cannot be null")
        self.__denominator = newDen
         
    def simplify(self): 
        if self.__numerator > self.__denominator:
            a = self.__numerator
            b = self.__denominator
        else:
            b = self.__numerator
            a = self.__denominator
        
        while True:
            rest = a % b
            if rest == 0: break
            a = b
            b = rest
        pgcd = b
        self.__numerator = int(self.__numerator / pgcd) 
        self.__denominator = int(self.__denominator / pgcd) 
            
    def __str__(self):  
        return "[%d/%d]" % ( self.__numerator, self.__denominator )
    
    def __add__(self, r2):
        return Rational(
            self.numerator*r2.denominator + self.denominator*r2.numerator,
            self.denominator * r2.denominator
        )

    # __sub__    __mul__   __truediv__
    
    def __lt__(self, r2):
        return self.numerator * r2.denominator < self.denominator * r2.numerator

    # __le__   __gt__   __ge__   __eq__   __ne__ 
    
    def toFloat(self):
        return self.__numerator / self.__denominator
        
    def __iter__(self):
        self.__iterPos = 0
        return self
    
    def next(self):                         # For Python 2.x
        return self.__next__()
    
    def __next__(self):                     # For Python 3.x
        self.__iterPos += 1
        if self.__iterPos == 1:
            return self.numerator
        elif self.__iterPos == 2:
            return self.denominator
        else:
            raise StopIteration()
    
    
if __name__ == '__main__':
    r1 = Rational(1, 2)
    print( "r1 == " + str( r1 ) )
    
    
    r2 = Rational(4,1)
    print( "r2 == " + str( r2 ) )
    
    r = r1 + r2
    print( "%s + %s == %s" % (r1, r2, r) )
    
    rToSimplify = Rational( 100, 50 )
    print( "[100,50] == %s" % ( rToSimplify ) )

    r = Rational()
    r.numerator = 33;
    r.denominator = 44;
    print( "Prop: %s" % str(r) )
    
    try: 
        rBad = Rational(0, 0)
    except RationalException as e:
        print("An error is raised")
Exemple de définition d'une classe Python de manipulation de fraction

Pour lancer cet exemple, veuillez procéder ainsi :

$> python3 Rational.py 
r1 == [1/2]
r2 == [4/1]
[1/2] + [4/1] == [9/2]
[100,50] == [2/1]
Prop: [33/44]
An error is raised
$>

Bien tester cette classe

Afin de bien tester cette classe, je vous propose de mettre en place une petite batterie de test unittest.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
import unittest

from Rational import Rational

class Test(unittest.TestCase):

    def setUp(self):
        print( "Avant chaque test" )

    def tearDown(self):
        print( "Après chaque test" )

    def testSimplify(self):
        r1 = Rational( 2*3*5*7*11, 3*5*7*13 )
        assert r1.numerator == 22
        assert r1.denominator == 13

    def testAddition(self):
        r1 = Rational(1,3)
        r2 = Rational(2,1)
        result = r1 + r2
        assert result.numerator == 7
        assert result.denominator == 3

    def testIterator(self):
        r1 = Rational(1,3)
        r1.__iter__()
        assert r1.__next__() == 1
        assert r1.__next__() == 3
        # Normalement on utilise l'itérateur ainsi : 
        # >>> for value in r1: print(value) 

    @unittest.expectedFailure
    def testBadDenominator(self):
        Rational( 1, 0 )
        

if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']
    unittest.main()
Quelques tests de notre classe via unittest

Une fois la classe de test codée, si vous utilisez l'IDE PyDev, cliquez avec le bouton droit de la souris sur le fichier de test (dans l'explorateur de projets). Sélectionnez-y "Run As", puis "Python Run". Cela lance le framework unittest. Une nouvelle vue (une nouvelle fenêtre) devrait s'ouvrir et afficher les résultats de votre batterie de tests. En voici une capture d'écran.

Unittest results