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 tutoriels/cours associés.
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 116 117 118 119 120 121 122 |
#!/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 //= pgcd # Division entière 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") |
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 $>
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() |
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.
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :