P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ WARNING: DATES IN CALENDAR ARE CLOSER THAN THEY APPEAR Articles | Connexion
 
~Les classes

Précédent  
  Python  
  Suivant
 Présentation

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


 Introduction

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".


 Quelle class !

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
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
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
       
      
JextCopier dans Jext
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()
       
      
JextCopier dans Jext
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()
       
      
JextCopier dans Jext
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)".


 Testament

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.):
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
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"
       
      
JextCopier dans Jext
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"
       
      
JextCopier dans Jext
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() .]
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
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:
    # nous avons trouvé le bon opérateur
       
      
JextCopier dans Jext
Vous trouverez la version complète du programme en fin d'article.


 Listings

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()
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Listing 2

class Operator:
  def getName(self):
      return ""
  def getOpsCount(self):
      return 0
  def compute(self, ops):
      return None
       
      
JextCopier dans Jext
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
       
      
JextCopier dans Jext

 Programme complet

#! /usr/bin/env python

##
## IMPORTS STATEMENTS
##
import math
import re
import sys

# OPERATOR CLASS
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

##
## We may wonder if operands counts tests are really necessary
## since the main loop *always* do pass the requested amount
## of parameters to the compute() method
##

# COSINE
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

# DIVIDE
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

# SUBSTRACTION
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

# MULTIPLY
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

# ADDITION
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

# SINE
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

# grammar
_nb_ = "\d+"
_op_ = [CosOperator(), DivideOperator(), MinusOperator(), MultiplyOperator(), 
PlusOperator(), SinOperator()]

# performs the computation
def compute(line):
    # the stack
    stack = []
    # tokens
    tokens = line.split()
    # state
    error = 0

    # stack pushes
    for token in tokens:

        # if the token is a number
        if re.compile(_nb_).search(token):
            stack.append(float(token))
        else:

            # we check if the token is an operator
            for op in _op_:
                if op.getName() == token:
                    # it is a valid operator
                    opList = []

                    # we check if the stack contains enough elements
                    if len(stack) >= op.getOpsCount():
                        # we pops the requested amount of operands
                        for i in range(op.getOpsCount()):
                            opList.append(stack.pop())
                        # compute
                        result = op.compute(opList)
                    else:
                        result = None
                        print op.getOpsCount() - len(stack),
                        "operands are missing"

                    # check result
                    if result:
                        stack.append(result)
                    else:
                        # error during calcul
                        print "Following operation caused an error:", token
                        error = 1
                    break
            else:
                # unknown operator
                print "Unknown keyword:", token
                error = 1

        if error:
            break
    else:
        print "Result:", stack.pop()

# main
def main():
    print "\n", sys.argv[0], "is an RPN computation program."
    print "(C)2001 Romain Guy - Type exit to quit."

    # RPNit can perform calculs from an argument or
    # from the standard input pipe
    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])

##
## MAIN ENTRY POINT
##
if __name__ == '__main__':
    main()

# End of rpnit.py
       
      
JextCopier dans Jext | Jext | Plugin Codegeek


par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006


Précédent  
  Python  
  Suivant

 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE