|
~Les classes

Nous avons appris à réaliser une calculatrice RPN grâce aux fonctions. Et si nous recommencions avec des classes ? 

Les termes "classe" et "objet" se réfèrent à la notion de programmation orientée objet. Cet article n'a pas pour vocation de vous enseigner les bases de la programmation objet (ou POO) mais bien de vous expliquer comment l'appliquer en Python. Pour ceux d'entre vous qui ne connaissent pas encore la POO, nous allons expliquer de manière succinte les deux termes cités précédemment.
Le terme "classe" se réfère à une définition logique d'une structure contenant des champs et des méthodes. Le nom "objet" désigne une instance d'une classe, c'est à dire une entité fonctionnelle correspondant aux caractéristiques définies par la classe. Dans la réalité, on pourrait considérer la classe "Voiture". Le mot "voiture" définit des objets à quatre roues pouvant avance, reculer, tourner, etc. Chaque voiture croisée dans la rue serait donc un objet de la classe "Voiture" puisque répondant aux caractéristiques requises.
La POO utilise également le principe d'héritage : une classe peut hériter d'une ou plusieurs autres classes. Si une classe "B" hérite d'une classe "A", alors la classe "B" proposera toutes les caractéristiques de "A" plus celles qui lui sont propres. Par analogie, la classe "A" pourrait être la classe "Véhicule" et la classe "B" la classe "Voiture". Une "Voiture" descend ou hérite de "Véhicule". On nomme alors "parent" la classe "Véhicule".
Après cette trop brève introduction aux concepts de POO, voyons comment définir nos propres classes en Python. Deux types de classes se voient réalisables en Python. Les premières correspondent aux classes "naturelles", soit celles qui ne possèdent pas de classe(s) parente(s).
class Voiture:
pass
Ces deux lignes permettent de définir une classe nommée "Voiture" ne contenant aucune caractéristique. L'ajout de caractéristiques passe par l'adjonction de champs de données (des variables) ou de méthodes (des fonctions Python) :
class Voiture:
def __init__(self):
self.model = "406"
def printModel(self):
print self.model
Chaque méthode d'une classe doit posséder le paramètre "self" en tant que premier paramètre. Ce mot-clé désigne l'instance courante de la classe. Ainsi, "self.model" pointe le champ "model" de l'objet exécuté. La fonction intitulée "__init__" joue un rôle particulier : elle se voit exécutée lors de la création d'un objet (donc d'une instance de la classe). Voici un exemple d'utilisation de notre classe :
v = Voiture()
v.printModel()
La création d'un objet se résout en écrivant une expression du type "objet = Classe()". La première instruction crée donc un objet "v" possédant les caractéristiques de "Voiture". La seconde ligne invoque la méthode "printModel()" qui affiche dans la console le contenu de la variable "model". Modifier cette variable s'avère possible :
v = Voiture()
v.printModel()
v.model = "306"
v.printModel()
Finalement, vous constaterez que les appels de méthodes ne précisent jamais le premier paramètre "self". Celui-ci se voit automatiquement ajouté par l'interpréteur Python. Aussi ne devez-vous pas en tenir compte lors des appels, même si sa déclaration se veut obligatoire. Les différentes méthodes de la classe sont aptes à recevoir différents arguments. La méthode "__init__" ne déroge pas à la règle. En ce cas, la création d'un objet ressemblera à cette expression : "objet = Classe(param1, param2)".
Nous savons qu'une classe peut hériter d'une ou plusieurs autres classes. Python accepte cet état de fait très simplement. L'héritage se définit ainsi :
class MaClasse(Parent1, Parent2.):
Reprenons notre exemple de "Vehicule" et de "Voiture". Le listing numéro un retrace le code source complet de l'héritage.
Dans cet exemple, la classe "Vehicule" offre une méthode intitulée "avance". Nous créons un objet "v" de la classe "Voiture". Etant donné que cette classe descend de "Véhicule", l'objet a accès aux méthodes de la classe parente. Nous pouvons alors écrire "v.avance()".
Lors du processus d'héritage, il est possible de modifier le comportement des méthodes parentes en les surchargeant. Si nous placions le code suivant dans la classe "Voiture", le comportement de "avance()" changerait :
def avance(self):
print "Je recule"
Il s'avère possible de faire référence à la méthode parente au sein de la méthode surchargée :
def avance(self):
Vehicule.avance(self)
print "mais en douceur"
La nouvelle méthode propose un double comportement. Notez que lors de l'appel de la méthode parente, l'indication de l'argument "self" se révèle indispensable.
Une calculatrice qui a la classe
Les classes vont nous permettre de définir les différentes opérations dont sera capable notre calculatrice. Chaque classe d'opération possédera trois méthodes communes : le retour de son nom, le retour du nombre d'opérandes souhaité et le retour du résulat du calcul. Ainsi, chacune de nos opérations héritera d'une classe parente commune, la classe "Operator". Sa définition se trouve dans le listing numéro deux. La classe "Operator" ne se verra jamais employée telle qu'elle. Ses différentes méthodes se contentent donc de retourner une valeur par défaut neutre.
Nous devons à présent définir chacune de nos opérations. Le listing numéro trois présente le code correspondant à l'opération d'addition. L'implémentation des méthodes "getName()" et "getOpsCount()" ne recèle aucune surprise : l'opérateur se nomme "+" et demande deux opérandes. La méthode "compute(self, ops)" part du principe que son second paramètre correspond à une liste d'opérande. Si le nombre d'opérande correspond à celui attendu, ici 2, la méthode renvoie le résultat de l'addition des deux premiers éléments de la liste.
Pour employer ces classes au sein du moteur de calcul, nous devons initialiser une liste contenant des instances de nos objets :
_ops_ = [PlusOperator(), MinusOperator() .]
La recherché d'un opérateur ne s'effectuera plus alors à l'aide d'un tableau à clé, mais par l'entremise d'une boucle for.
for op in _ops_:
if op.getName() == token:
Vous trouverez la version complète du programme en fin d'article.
Listing 1
class Vehicule:
def avance(self):
print "J'avance"
class Voiture(Vehicule):
def __init__(self, model):
self.model = model
def printModel(self):
print self.model
v = Voiture("Espace")
v.avance()
Listing 2
class Operator:
def getName(self):
return ""
def getOpsCount(self):
return 0
def compute(self, ops):
return None
Listing 3
class PlusOperator(Operator):
def getName(self):
return "+"
def getOpsCount(self):
return 2
def compute(self, ops):
if len(ops) == 2:
return ops[1] + ops[0]
else:
return None
import math
import re
import sys
class Operator:
"This class defines an operator for the calculator."
def getName(self):
"returns the operator itself"
return " "
def getOpsCount(self):
"returns the number of requested operands"
return 0
def compute(self, ops):
"performs computation and returns result"
return None
class CosOperator(Operator):
def getName(self):
return "cos"
def getOpsCount(self):
return 1
def compute(self, ops):
if len(ops) == 1:
return math.cos(ops[0])
else:
return None
class DivideOperator(Operator):
def getName(self):
return "/"
def getOpsCount(self):
return 2
def compute(self, ops):
if len(ops) == 2:
if ops[0] == 0:
return None
else:
return ops[1] / ops[0]
else:
return None
class MinusOperator(Operator):
def getName(self):
return "-"
def getOpsCount(self):
return 2
def compute(self, ops):
if len(ops) == 2:
return ops[1] - ops[0]
else:
return None
class MultiplyOperator(Operator):
def getName(self):
return "*"
def getOpsCount(self):
return 2
def compute(self, ops):
if len(ops) == 2:
return ops[1] * ops[0]
else:
return None
class PlusOperator(Operator):
def getName(self):
return "+"
def getOpsCount(self):
return 2
def compute(self, ops):
if len(ops) == 2:
return ops[1] + ops[0]
else:
return None
class SinOperator(Operator):
def getName(self):
return "sin"
def getOpsCount(self):
return 1
def compute(self, ops):
if len(ops) == 1:
return math.sin(ops[0])
else:
return None
_nb_ = "\d+"
_op_ = [CosOperator(), DivideOperator(), MinusOperator(), MultiplyOperator(),
PlusOperator(), SinOperator()]
def compute(line):
stack = []
tokens = line.split()
error = 0
for token in tokens:
if re.compile(_nb_).search(token):
stack.append(float(token))
else:
for op in _op_:
if op.getName() == token:
opList = []
if len(stack) >= op.getOpsCount():
for i in range(op.getOpsCount()):
opList.append(stack.pop())
result = op.compute(opList)
else:
result = None
print op.getOpsCount() - len(stack),
"operands are missing"
if result:
stack.append(result)
else:
print "Following operation caused an error:", token
error = 1
break
else:
print "Unknown keyword:", token
error = 1
if error:
break
else:
print "Result:", stack.pop()
def main():
print "\n", sys.argv[0], "is an RPN computation program."
print "(C)2001 Romain Guy - Type exit to quit."
if len(sys.argv) < 2:
line = ""
while line != "exit\n":
sys.stdout.write("> ")
line = sys.stdin.readline()
if line != "exit\n":
compute(line)
else:
compute(sys.argv[1])
if __name__ == '__main__':
main()
|