reseau social

Découverte de Python, 2nd partie

Un article de ToutProgrammer.com.

Dans la première partie, Erwan Loisant nous proposait une courte présentation des bases du langage Python. Dans cette seconde partie, nous allons découvrir la programmation orientée objet avec Python.

Sommaire

[modifier] Introduction

Bien que n'étant pas basé sur la programmation orientée objet, Python permet d'utiliser ces concepts. De plus, Python étant un langage adapté aux débutants, j'introduis ici les concepts objets. Vous pouvez aborder cet article sans connaître l'objet !

Mieux vaut tard que jamais, c'est après plusieurs mois que sort la deuxième partie de notre introduction à Python.

Dans l'article précédent nous avons découvert ensemble le langage Python. Si la programmation orientée objet n'est pas intrinsèque au langage, comme c'est le cas de Java, son utilisation permet de gagner en lisibilité, réutilisabilité et sécurité (par rapport aux éventuels bogues).

[modifier] La programmation orientée objet, ou POO

Aujourd'hui, de nombreux langages sont des langages à objets : C++, Java, ObjectiveC et C# sont peut-être les principaux. La majeure partie des langages de script récents (tels que Python, bien sûr mais aussi Ruby) proposent également des notions objets.

Il faut noter qu'être "orienté objet" ne définit pas la nature du langage, mais plutôt une orientation, une facilité de développement. Ainsi, Smalltalk et C++ sont tous les deux parfaitement objets alors que le premier est un langage fonctionnel (issu de Lisp) et le deuxième un langage impératif (issu de Fortran).

Il s'agit avant tout de créer une meilleure représentation des données, et surtout rassembler données et traitements dans un même ensemble. Un autre aspect important de la programmation orienté est l'encapsulation des données, afin qu'à l'extérieur d'un module seul ce qui est nécessaire est visible.

[modifier] Classe

En programmation orientée objet, on passe la plupart de son temps à écrire des classes. Une classe est en quelque sorte un type de données plus évolué, qui intègre non seulement les données mais aussi les traitements.

[modifier] Instance

Une fois les classes écrites (décrites), on crée des instances. Une instance est en quelque sorte une matérialisation d'une classe. Par exemple, si on considère une classe ENTIER permettant de décrire des nombres entiers, l'objet représentant le nombre "5" sera une instance de la classe ENTIER.

[modifier] Variables et méthodes

Une classe contient donc des variables et des méthodes : les variables contiennent les données et les méthodes les traitements.

Prenons un exemple : une classe permettant de représenter une température. La température est intéressante car elle peut se représenter de trois façons : en Kelvin, en degrés Celsius ou en degrés Fahrenheit.

  • variable de classe : "valeur" est la valeur de la température. On aura une valeur numérique représentant la température. Pour des facilités de calcul mais aussi car il s'agit du standard international, cette valeur sera exprimée en Kelvin.
  • méthodes de classe : "inK(), inC(), inF()" renvoient la valeur de la température en Kelvin, en degrés Celsuis ou en degrés Fahrenheit.
  • méthodes de classe : "setK(), setC(), setF()" sont les fonctions duales des précédentes. Elle permettent de fixer la valeur de notre instance température.

On aperçoit déjà un intérêt à la programmation orientée objet : on a des conversions à faire, qui pourraient bien compliquer la tâche de tout programmeur qui aurait à traiter des températures. Ici, les conversions sont concentrées dans la classe elle-même.

On peut affecter une valeur en degrés Celsius à notre instance de température, puis regarder la valeur en degrés Fahrenheit sans avoir à se préoccuper des conversions. Remarquez d'ailleurs qu'on peut travailler sur notre objet avec ces six méthodes sans toucher à la variable, nous y reviendrons.

[modifier] Constructeurs

Pour créer une instance, il nous faut passer par une méthode particulière appelée constructeur. Dans la majorité des langages de programmation il est possible de définir plusieurs constructeurs mais ce n'est pas le cas de Python.

Ici, notre constructeur prendra en entrée une valeur de température en Kelvin pour créer une instance.

[modifier] Revenons à Python

La théorie est intéressante, mais voyons un peu ce que cela donne en Python. Une variable ou méthode de classe dépendant généralement d'une instance, en Python comme dans beaucoup de langages orientés objet on utilise une notation pointée pour représenter cela. Ainsi, si on définit une instance de Temperature qui s'appelle eauChaude, on accédera à la température en degrés Celsius par eauChaude.inC(). Voyons maintenant comment définir cette classe.

  1. class Temperature:
  2. # CONSTRUCTEUR
  3. def __init__(self, laValeur):
  4. self.valeur = laValeur
  5.  
  6. # OBVERVATEURS
  7. def inK(self):
  8. return self.valeur
  9. def inC(self):
  10. return self.valeur - 273.16
  11. def inF(self):
  12. return 1.8 * self.inC() + 32
  13.  
  14. # MODIFIEURS
  15. def setK(self):
  16. self.valeur
  17. def setC(self):
  18. return 0 # A faire en exercice !
  19. def setF(self):
  20. return 0 # A faire en exercice !
  21.  
  22. # Exemple d'utilisation de cette classe
  23. eauChaude = Temperature(310)
  24. print eauChaude.inC()
  25. print eauChaude.inF()

Il y a quelques points à noter:

  • la première chose à noter est la méthode __init()__, ligne 3 : c'est le constructeur. Toute instance sera créée par l'appel à cette méthode. La méthode doit porter ce nom, cela fait partie de la syntaxe Python ; Par contre, elle n'est pas appelée comme les autres méthodes, mais simplement par le nom de la classe (ligne 23).
  • ensuite, le mot-clef self : il est primordial pour la programmation orientée objet en Python. Le mot-clef selfdésigne l'instance en question. Par exemple, quand on fait un appel depuis l'instance "eauChaude", "self" réfère à "eauChaude".
  • on retrouve self dans la signature de toutes les méthodes d'instance définies dans notre classe. C'est logique puisque la méthode en question dépend de l'instance, l'instance est bien un paramètre de la méthode. Cependant, l'instance n'apparaît pas dans les parenthèses lors de l'appel puisqu'elle est indiquée par la notation pointée (lignes 24 et 25).

[modifier] Privé, public ?

Avant d'aller plus loin en abordant le principe de l'héritage, réglons un point important : nous avons prévu une interface à notre classe (par des observeurs et des modifieurs) mais que se passe-t-il si un développeur utilisant notre classe accède directement à la valeur en Kelvin par eauChaude.valeur ? Il peut très bien lui affecter par erreur une valeur en degrés celsius, ce qui n'arriverait pas avec notre interface aux noms explicites. Dans ce cas ça reste de faible gravité, mais dans une classe où les variables dépendent les unes des autres cela peut être catastrophique et mener à un état aberrant.

De plus, lorsqu'on accède aux variables uniquement par les observeurs et les modifieurs, on s'assure que la variable n'est modifiée que si le modifieur est appelé explicitement. Un développeur souhaitant simplement accéder à la valeur ne peut pas la modifier par erreur, par exemple en confondant = (l'affectation) et == (la comparaison).

Pour le bien du développeur en question, nous devons cacher la variable, la rendre privée. Pour cela, il suffit de lui donner un nom commençant par deux tirets bas.

3 def __init__(self, laValeur):
4 self.__valeur = laValeur

Cela peut aussi être utilisé pour des méthodes : on peut vouloir créer une méthode privée pour automatiser une tâche répétitive mais que le développeur utilisant notre classe n'a pas besoin de connaître.

De la même façon, il est d'usage de préfixer tout membre d'une classe d'un tiret. Cela ne change rien pour l'interpréteur mais cela permet au développeur de bien voir qu'il s'agit d'un membre de classe publique, et non d'une fonction indépendante.

[modifier] Héritage

Nous abordons maintenant un point important de la programmation orientée objet, bien que souvent délaissé par les industriels, l'héritage. Cela permet de spécialiser une classe en changeant certaines méthodes ou en ajoutant, cela sans toucher le code source de la première classe.

Laissons de côté les températures qui se prêtent peu à l'héritage et intéressons-nous aux chaînes de caractères. Il existe un type de base pour les chaînes de caractères, on peut donc les traiter directement. Nous utiliserons cependant la classe str qui permet de travailler sur des objets.

Exemple d'utilisation de str :

1 vent = str("vent divin")
2
3 print vent, vent.upper()

=> vent divin VENT DIVIN

En appellant le constructeur de str, nous obtenons donc une instance de cette classe sur laquelle on peut utiliser toutes les méthodes définies par défaut par Python.

Maintenant, supposons que nous voulions représenter des chaînes de caractères japonaises. Pas de problèmes : Python gère parfaitement l'unicode, il suffit d'utiliser cette même classe. Cependant, le japonais a un systeme d'écriture bien particulier : il est composé de caractères chinois (les kanji) et d'autres purement japonais (les furigana). Si les seconds représentent des syllabes et sont en nombre restreints, les premiers représentent des concepts abstraits ou concrets et il en existe quelques milliers.

Par exemple, "kamikazé" est composé de deux kanji : kami (dieu) et kazé (le vent). Ce mot fait référence à un typhon qui a balayé l'envahisseur Mongol il y a quelques siècles, ce "vent divin" était donc une force venue du ciel pour balayer les ennemis du Japon. On peut aussi l'écrire en furigana, il y a alors quatre caractères : ka, mi, ka et zé.


Nous voulons donc, dans une classe jaStr, ajouter des méthodes pour différencier les kanji des furigana. Afin d'éviter de réécrire la panoplie de méthodes fournie par Python, nous allons utiliser l'héritage. Pour que le code soit lisible à tous même si les polices japonaises ne sont pas installées, les furigana sont remplacés par des caractères latins. Dans la pratique, le code source serait en unicode avec les caractères japonais directement saisis.

[modifier] La classe jaStr

Voici le code de la classe:

  1. tousLesKana = ['a', 'ka', 'sa', 'ta', 'ha', 'ma', 'ya', 'ra', 'wa',
  2. 'i', 'ki', 'si', 'ti', 'hi', 'mi', 'ri',
  3. 'u', 'ku', 'su', 'tu', 'hu', 'mu', 'yu', 'ru', 'wu',
  4. 'e', 'ke', 'se', 'te', 'he', 'me', 're',
  5. 'o', 'ko', 'so', 'to', 'ho', 'mo', 'yo', 'ro', 'wo', 'n']
  6.  
  7. class jaStr(str):
  8. def hiraganaExistent(self):
  9. for kana in tousLesKana:
  10. if str.count(self, kana) > 0:
  11. return true
  12. return false
  13.  
  14. def hiraganaSeulement(self):
  15. for kana in tousLesKana:
  16. if str.count(self, kana) > 0:
  17. return false
  18. return true

L'intérêt principal de cette approche est que l'on pourra toujours utiliser les méthodes de la classe str sur les instances de la classe jaStr.

Remarquez aussi la forme de l'appel à une méthode de la classe mère à l'intérieur de la définition de la classe elle-même, ligne 10. La méthode est préfixée du nom de la classe mère et self est ajouté comme argument ; rappelons que self pointe vers l'instance actuelle.

[modifier] Épilogue

Ce tour d'horizon, loin d'être exhaustif, vous a donné de quoi écrire des classes solides en Python. Vous pouvez donc vous lancer dans ce monde merveilleux de la programmation orientée objet qui, alliée à la simplicité de Python, permet de réaliser très rapidement des composants logiciels des plus utiles.

Avant que vous ne vous lanciez dans un codage effréné, j'aimerais quand même vous donner quelques conseils. Pour garder une bonne lisibilité dans le code source et permettre un débuggage efficace, je vous conseille d'éviter les variables globales. Si vous commencez à faire de l'objet, il est préférable de tout mettre dans les classes. De plus, Python permet de définir les variables à n'importe quel moment. Il reste préférable d'initialiser toutes ses variables dans le constructeur, sinon gare aux surprises et aux variables perdues

Bon codage, et à une prochaine fois pour un autre aspect de Python !

[modifier] Historique de l'article

Cet article, réalisé par Erwan LOISAN, a été publié pour la première fois le 11 février 2001 sur le site LinuxFrench.net sous licence Creative Commons License Non Commerciale.