V. Modulariser▲
V-A. Regrouper dans des fonctions▲
À l'heure actuelle, nos programmes sont composés de petites suites d'opérations qui s'enchaînent les unes à la suite des autres dans le même ordre. Nous avons vu que pour répéter une opération nous pouvions utiliser des boucles. Mais cela ne change rien au fait que l'enchaînement est parfaitement continu. Or, il est souvent nécessaire de répondre à une action de l'utilisateur effectuée à un moment donné, parmi d'autres. Chaque action faisant appel à un bloc d'instruction, chaque action pouvant être déclenchée par l'utilisateur à certains moments ou à tous moments, il est important d'avoir une conception des instructions qui ne soient pas complètement linéaires. Pour cela, nous utilisons des fonctions : ce sont des blocs d'instructions auxquels nous avons attribué un nom de façon à pouvoir les appeler, les déclencher au moment opportun. Cela prend la forme suivante :
def
nomdelafonction
(
):
bloc d'instruction
Il s'agit là de la portion de Python qui définit ou déclare la fonction, un peu comme on déclare une variable. Elle commence par le mot clé def suivi du nom de la fonction (selon les mêmes règles de nommage que les variables d'environnement), suivie elle-même de parenthèses qui permettent de la différencier de simples variables (et nous verrons qu'elles pourront être autrement utiles) et d'un : qui mentionne le début d'un bloc. Le bloc qui suit doit être indenté et toutes les lignes qui la composent indentées de façon similaire, comme dans le cas des boucles.
Pour appeler la fonction, nous noterons simplement son nom sous la forme :
nomdelafonction
(
)
un appel de fonction peut être réalisé à n'importe quel moment du programme, y compris à partir d'une boucle, voire d'une autre fonction.
V-B. Rendre les fonctions plus universelles avec des paramètres▲
L'avantage d'une fonction est de pouvoir être réutilisée dans différents contextes. Mais en imaginant une fonction qui affiche un texte du type :
def
affiche
(
):
print
"Bonjour"
que faire si nous souhaitons aussi afficher « bonsoir » ? Faut-il créer une autre fonction ? Et ainsi les cumuler au point de s'y perdre. Il semble évident que non. Python, comme tous les langages modernes, permet l'utilisation de paramètres. Ces paramètres sont des valeurs associées à des variables qui seront transmises directement à la fonction.
Les paramètres sont en général déclarés dans la déclaration de définition et remplacent les valeurs constantes dans le bloc. On obtient ainsi :
def
affiche
(
mot):
print
mot
et pour appeler la fonction :
affiche
(
"bonjour"
)
> bonjour
affiche
(
"bonsoir"
)
> bonsoir
Si plusieurs paramètres sont nécessaires, il suffit de les séparer par des virgules dans la déclaration et dans l'appel :
def
affiche
(
mot, nom):
print
mot +
', '
+
nom
affiche
(
"Bonjour"
, "Richard"
)
ce qui est encore plus intéressant en jouant avec des valeurs inconnues saisies par l'utilisateur, récupérées dans des sources de données ou autre :
def
affiche
(
mot,nom):
print
mot+
', '
+
nom
print
"Quel mot afficher ? "
mot =
raw_input(
)
print
"pour qui ?"
nom =
raw_input(
)
affiche
(
mot,nom)
enregistré dans un fichier et lancé :
> Quel mot afficher ?
Bonjour
> pour qui ?
Richard
> Bonjour, Richard
Attention cependant c'est l'ordre des paramètres qui compte, pas leur nombre :
affiche(nom,mot)
> Richard, Bonjour
V-C. Paramètres inconnus▲
Lorsque le nombre des paramètres est inconnu, par exemple lorsqu'ils sont issus d'une liste, nous sommes face à un problème : il n'est pas toujours possible de les déclarer ou de les appeler.
De fait, nous avons deux possibilités qui répondent à des besoins différents. Si nous connaissons le nombre de paramètres mais ne savons pas s'il sera toujours possible de leur affecter une valeur, ou si nous ignorons complètement la nature et le nombre des paramètres.
Dans le premier cas nous pourrons attribuer des valeurs par défaut :
def
(
nom, mot=
"Bonjour"
):
print
mot+
', '
+
nom
affiche
(
"Richard"
)
> Bonjour, Richard
affiche
(
"Richard"
,"Bonsoir"
)
> Bonsoir, Richard
voire de façon plus explicite pour éviter tout risque de confusion en particulier si le nombre de paramètres :
affiche
(
"Richard"
, mot=
"Bonsoir"
)
Dans le second cas, nous placerons une étoile (liste) ou deux (dictionnaires) devant un paramètre référent :
liste1 =
[5
,10
,3
,7
]
def
moyenne
(*
num):
print
"La moyenne est de "
+
sum(*
num)/
len(*
num)
moyenne
(
liste2)
> La moyenne est de 6
V-D. Réutiliser le résultat d'une fonction▲
Le bloc d'instruction va effectuer une ou plusieurs actions. le résultat de ses actions ne peut être utilisé qu'en interne ou avoir besoin de resservir dans une autre partie du programme. Imaginons par exemple un jeu dans lequel nous devons mémoriser un score suite à des choix opportuns ou non. Chaque choix déclenche l'ajout ou la suppression de point, et le total doit être réutilisé dans les actions suivantes. Nous pourrions dans ce cas avoir une fonction par action et une fonction pour le calcul des points. Les actions envoient leur résultat à la fonction calculant les points pour effectuer le calcul adéquat. Le total peut lui-même être utilisé pour donner des bonus ou faire des changements de niveau.
Chaque total peut donc être enregistré dans une variable globale ou renvoyé à la fonction qui l'appelle afin qu'elle en tienne compte dans le déroulé des actions du jeu.
Un cas simple : écrire un résultat provenant d'un calcul utilisé dans une fonction.
L'exemple de base pourrait être :
def
calcul
(
param1):
param2=
10
print
"le résultat est "
+
str(
param1+
param2)
calcul
(
5
)
> le résultat est 15
on pourrait remplacer cet exemple par
def
calcul
(
):
return
param1+
param2
print
"le résultat est "
+
str(
calcul
(
5
))
> le résultat est 15
L'avantage de cette méthode est d'offrir une meilleure réutilisation de la fonction. Dans le premier cas, la fonction calcule et affiche le résultat elle-même, ce qui la rend inexploitable dans le cas où le calcul seul doit être effectué.
Dans le second cas, la fonction ne fait que le calcul et laisse le soin au contexte de savoir qu'en faire.
V-E. Faciliter la réutilisation avec les classes▲
De nombreuses personnes utilisent Python sans utiliser de classes et pourtant l'un des premiers arguments que l'on avance en présentant Python est son orientation 100 % objet. Il pourra peut-être vous sembler étrange de se passer d'une telle fonction du langage si elle est tellement centrale, voire comment certains font pour s'en passer.
C'est une question que je me pose régulièrement : pourquoi se passer de la programmation objet alors qu'elle est si simple, si pratique et offre finalement et sans effort de nombreuses possibilités.
Moi qui écris ces lignes et qui ne suis que simple graphiste ou artiste et loin d'être un programmeur hors-pair comme d'autres peuvent s'en vanter, voilà pourtant que, si je devais faire des statistiques de mon code produit, l'utilisation de la programmation objet représenterait au moins 90 % des lignes écrites et surtout conservées. J'espère que vous n'en conclurez pas que, comme je ne suis pas un expert, je me trompe. Si vous avez des doutes, il vous suffira de faire une petite recherche sur Internet et vous vous rendrez rapidement compte que les discours sont en faveur de la programmation objet.
Finalement, le point à retenir est que si je peux le faire, vous le pouvez, qui que vous soyez.
La programmation orientée objet est une forme supplémentaire d'organisation du code produit. Jusqu'ici, nous avons utilisé des blocs d'instructions, puis des fonctions, en passant par des variables. Le plus évolué en termes de structuration est sans conteste, jusqu'à présent, l'utilisation de fonctions. Cependant, nous avons des petits soucis avec ces fonctions. Par exemple, si nous programmons un site Internet, nous avons besoin d'une fonction qui vérifie si un utilisateur est connecté et une autre qui liste les fichiers d'un répertoire. A priori, ces deux fonctions n'ont rien à voir ensemble et elles seront cependant accessibles de la même façon, au même niveau. Un peu comme si vous rangiez les torchons avec les serviettes (je sais vous le faites peut-être et moi aussi, mais ce n'est pas une excuse).
En programmation objet, nous allons ajouter au-dessus de la fonction habituelle def un élément qui va permette de classer les fonctions selon leur contexte d'utilisation. Parti de ce principe simple, beaucoup de choses vont alors devenir possibles.
V-F. Définir des classes▲
Définir une classe se fait simplement en utilisant le mot réservé class suivi du nom personnalisé de la classe en tant que début de bloc d'instruction :
class
MaClasse :
#instructions
En ce qui concerne le nom de la classe, il est de coutume de respecter toujours la même règle, par simplicité. Nous utilisons ici un nom composé sans séparation entre les termes mais avec une majuscule au début de chacun d'entre eux.
Une fois la classe créée, il va être possible de l'instancier, c'est-à-dire de l'utiliser dans un cas concret que l'on pourra appeler objet, tout comme chaque table est une instance de ce que peut être une table.
L'instanciation se fait simplement en faisant une affectation :
objet =
MaClasse
(
)
Malheureusement, si vous essayez ces quelques lignes dans l'interpréteur Python, vous n'obtiendrez pas grand-chose :
class
MaClasse :
#instructions
File "<stdin>", line 3
^
IndentationError: expected an indented block
Le message n'est pas très explicite, mais il mentionne bien qu'il manque quelque chose à cette classe : nous devrons donc lui ajouter des méthodes et des attributs.
V-G. Ajouter des attributs▲
À quoi sert de créer des classes si elles ne contiennent rien ? A priori à rien. La première tâche fréquente dans la définition des classes est souvent la définition de variables, comme pour toute autre structure. On appellera alors la variable « attribut » car elle devient une propriété liée en propre à l'objet et n'existe que dans son contexte.
class
MaClasse :
essai =
"ok"
Notez au passage l'indentation, car la classe agit comme un bloc d'instruction structuré. Il faudra donc être vigilant à ce niveau.
Essayons maintenant de l'instancier.
objet =
MaClasse
(
)
print
objet.essai
Affiche : ok
Remarquez lors de l'instanciation, des parenthèses sont ajoutées au nom de la classe et aussi que pour faire référence à une propriété, il faut que :
- celle-ci soit définie dans la classe ;
- l'objet qui la concerne soit instancié ;
- la rattacher à l'instance en plaçant un point entre les deux.
pour vérifier le type il est toujours possible d'utiliser la méthode type :
type(
MaClasse)
<type 'classobj'>
Ainsi, l'attribut essai se retrouve associée à l'instance et il peut y avoir autant d'attributs du même type qu'il y a d'instance :
class
MaClasse :
essai =
"ok"
objet =
MaClasse
(
)
print
objet.essai
> ok
objet2 =
MaClasse
(
)
print
objet2.essai
> ok
objet2.essai =
"wow"
print
objet2.essai, objet.essai
> wow ok
Nous utiliserons régulièrement le signe > en début de ligne pour montrer ce que l'interpréteur renvoie. Cette pratique est inverse à l'interpréteur Python mais nous permet, nous l'espérons, rendre plus claire les lignes de codes qui nous intéressent.
On voit bien dans cet exemple que la valeur de l'attribut a pu être modifiée dans le bloc principal avec une simple affectation et que pourtant, l'affectation ayant été effectuée dans le contexte d'une instance, seule la valeur associée à l'attribut de cette instance a été réellement affectée, l'autre instance n'ayant pas été modifiée le moins du monde.
V-H. Ajouter des méthodes▲
Il en est des fonctions comme des variables. Dans le cadre d'une classe, elles deviennent des « méthodes ». Cette différence de terme peut sembler capricieuse, elle permet cependant de toujours bien savoir à quel niveau on parle.
V-H-1. Des méthodes standards▲
Les méthodes vont permettre de définir des regroupements de fonctions propres à notre contexte de manière à mieux les exploiter, en respectant systématiquement ce contexte.
La définition d'une fonction interne à une classe est très simple, puisqu'elle ne contient aucune différence avec la façon standard de la définir : on utilise le mot clé def suivi du nom de la fonction, un ":", puis à la ligne le bloc d'instruction indenté.
class
MaClasse:
def
affiche
(
self):
essai=
"ok"
return
essai
objet =
MaClasse
(
)
print
objet.affiche
(
)
> ok
V-H-2. _init__ et self, une méthode particulière▲
__init__ est une méthode particulière qui est lancée automatiquement lors de la déclaration d'une instance. On l'appelle « constructeur » parce qu'elle aide à poser les bases de toute l'instance. On y déclare en général les principaux attributs et on peut éventuellement y commencer du traitement par appel de certaines méthodes.
Dans l'exemple qui précède, l'utilisation du constructeur pourrait conduire à un programme comme celui-ci :
class
MaClasse:
def
__init__
(
self):
self.essai=
"ok"
def
affiche
(
self):
return
self.essai
objet =
MaClasse
(
)
print
objet.affiche
(
)
> ok
Le constructeur contient en général au minimal l'affectation de base des premières variables les plus importantes du programme de manière à s'assurer qu'elles sont bien déclarées et seront parfaitement partagées par la suite au sein des différentes méthodes.
Le mot-clé self permet quant à lui, de spécifier que l'action s'effectue bien au niveau de l'instance elle-même. Nous aborderons plus abondamment ce sujet en parlant ultérieurement de leur opposé, les attributs de classe.
Enfin, si __init__ est un constructeur, __del__ est un destructeur. Plus rarement utilisé en Python puisque l'interpréteur prend en charge la destruction des objets lorsqu'ils deviennent inutilisés, le destructeur permet cependant de mieux contrôler les événements et de déclencher éventuellement des actions à la destruction d'une instance, comme renvoyer un message, par exemple.
class
MaClasse:
def
__init__
(
self):
self.essai=
"ok"
def
affiche
(
self):
return
self.essai
def
__del__
(
self):
print
"objet détruit"
objet =
MaClasse
(
)
print
objet.affiche
(
)
> ok
objet.__del__
(
)
> objet détruit
Ici, nous forçons la destruction de l'objet pour l'exemple, mais cette action est bien sûr réalisable dans tout projet, même si elle n'est pas nécessaire.
V-I. Manipulations d'attributs▲
V-I-1. Manipuler les attributs▲
Jusqu'ici, nous avons observé que les attributs auront des valeurs redéfinissables pour chaque objet. Mais qu'en est-il si l'on souhaite avoir une valeur partagée entre plusieurs instances ou à l'inverse gagner un peu en définissant spécifiquement les valeurs pour une instance.
V-I-2. Attribut de classe▲
Nous avons manqué un peu de précision en définissant précédemment les attributs. Nous avons présenté notre attribut essai de la même façon alors qu'il est apparu dans deux contextes différents : celui de la racine de la classe et celui de la méthode. Nous avons mentionné que cet attribut était lié à l'instance, ce qui n'est pas tout à fait juste. Prenons le point suivi pour aborder le sujet : si à l'inverse, nous souhaitions que la même valeur soit partagée par plusieurs instances, la syntaxe de l'attribut devrait être un peu différente : on le définira en début de classe en dehors de toute méthode ou on le préfixera avec le nom de la classe de manière que l'attribut y soit explicitement associé, et non pas aux instances :
class
MaClasse :
essai =
"ok"
def
affiche
(
self):
print
essai
objet =
MaClasse
(
)
objet2 =
MaClasse
(
)
objet.affiche
(
)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in affiche
NameError: global name 'essai' is not defined
On voit dans cet exemple qu'un petit problème se pose : il n'est pas possible d'accéder à l'attribut essai à partir de la méthode affiche. La première solution qui pourrait nous venir à l'esprit serait d'utiliser le mot clé self pour spécifier l'appartenance de l'attribut, mais voici :
class
MaClasse :
essai =
"ok"
def
affiche
(
self):
print
self.essai
objet =
MaClasse
(
)
objet.affiche
(
)
> ok
objet2 =
MaClasse
(
)
objet2.affiche
(
)
> ok
objet2.essai =
"wow"
objet2.affiche
(
)
> wow
objet.affiche
(
)
> ok
Nous nous retrouvons alors dans la même configuration que précédemment et ne résolvons pas notre problème : l'attribut est associé à l'instance et n'est pas partagé avec celle-ci.
La solution consiste à associer explicitement l'attribut à la classe en le pointant comme tel. Au lieu d'utiliser le mot-clé self, nous utiliserons alors le nom de la classe pour bien préciser que c'est à son niveau que les opérations vont s'effectuer.
class
MaClasse:
def
__init__
(
self):
MaClasse.essai =
"ok"
objet =
MaClasse
(
)
objet2 =
MaClasse
(
)
print
objet.essai
> ok
print
objet2.essai
> ok
print
MaClasse.essai
> ok
MaClasse.essai =
"wow"
print
objet.essai
> wow
print
objet2.essai
> wow
Ici, les deux instances partagent donc bien le même attribut et changer la valeur de cet attribut en la préfixant du nom de la classe, même au moment de la réaffectation, permet bien de spécifier que ce n'est pas au niveau de l'instance que les choses se jouent et que par conséquent, toutes les instances hériteront de cette nouvelle valeur.
Si les choses ne sont pas clairement définies, il est possible de se retrouver avec de petites surprises. Continuons dans la foulée des lignes précédentes :
class
MaClasse :
essai =
"ok"
def
affiche
(
self):
print
self.essai
objet =
MaClasse
(
)
objet.affiche
(
)
> ok
objet2 =
MaClasse
(
)
objet2.affiche
(
)
> ok
objet2.essai =
"wow"
objet2.affiche
(
)
> wow
objet.affiche
(
)
> ok
print
objet2.essai
> wow
MaClasse.essai =
"test"
objet.affiche
(
)
> test
objet2.affiche
(
)
> wow
À interpréter cet exemple, on voit que l'attribut de classe qui a été redéfini dans le cadre d'une instance, ne prend plus en compte l'affectation de valeur faite à cet attribut de classe.
Ainsi il y a deux essais différents : celui de la classe créée en début de classe ou explicitement et l'attribut de la méthode. La méthode affiche fait référence à son propre essai qui hérite de celui de la classe au moment de la construction de l'instance, mais qui peut être redéfini séparément. Pour faire explicitement référence à l'attribut de classe, nous aurions dû écrire print MaClasse.essai et non pas self.essai.
V-J. Privatiser attributs et méthodes▲
Le but de la programmation objet est de mieux structurer le code pour mieux contrôler les contextes d'exécution. En effet, tout programme commence avec quelques petites lignes et finit souvent par plusieurs milliers et il devient alors plus difficile de retrouver ces marques et de bien continuer.
Les défauts de conception n'apparaissent en général pas au début des projets mais bien à la suite, lors des modifications successives, éventuellement réalisées par plusieurs personnes qui ont chacune leur vision des choses.
Différencier les attributs de classe et d'instance est une première approche améliorée, mais il est aussi possible de définir explicitement ce qui peut être de l'extérieur de l'objet. Jusqu'à présent, tout attribut et toute méthode peuvent être appelés du programme principal. Alors que dans la pratique certains d'entre eux seront certainement créés pour un usage interne en prétraitement d'informations. Ces attributs n'ont pas besoin, et ne devraient donc pas laisser cette tentation d'être accessible de l'extérieur.
Un petit exemple pour voir de quoi il s'agit :
class
MaClasse:
def
__init__
(
self):
self.__essai=
"Bonjour "
self.essai=
"le monde"
def
affiche
(
self):
print
self.__essai
def
montre
(
self):
print
self.__essai
objet=
MaClasse
(
)
print
objet.essai
Le résultat sera bien : le monde.
Maintenant essayons avec :
print
objet.__essai
File "private_public.py", line 15, in <module>
print objet.__essai
AttributeError: MaClasse instance has no attribute '__essai'
L'interpréteur nous informe que la classe n'a pas d'attribut de ce nom. Ce n'est évidemment pas exactement vrai, mais il garde le secret sur la possibilité de manière à ne pas nous tenter : __essai est privé et ne peut être utilisé que depuis une méthode de la classe :
objet.affiche()
> Bonjour
Comment avons-nous fait pour rendre cette variable privée ? Nous avons simplement rajouté deux _ en début de nom. Remarquez au passage que essai et __essai sont considérés comme étant deux attributs différents par l'interpréteur alors que pour le lecteur ils seront évidemment très similaires.
La même opération peut être réalisée avec les méthodes :
class
MaClasse:
def
__init__
(
self):
self.__essai=
"Bonjour "
def
affiche
(
self):
print
self.essai
def
__montre
(
self):
print
self.__essai, self.essai
objet=
MaClasse
(
)
objet.__montre
(
)
File "private_public.py", line 15, in <module>
objet.__montre
(
)
AttributeError: MaClasse instance has no attribute '__montre'
alors que :
class
MaClasse:
def
__init__
(
self):
self.__essai=
"Bonjour "
self.essai=
"le monde"
def
affiche
(
self):
self.__montre
(
)
def
__montre
(
self):
print
self.__essai, self.essai
objet=
MaClasse
(
)
objet.affiche
(
)
renverra :
Bonjour le monde
En fait, il existe plusieurs notations. On trouvera souvent référence à _variable comme étant une convention d'écriture. Cette convention était utilisée à titre mnémotechnique dans les moments où Python ne gérait pas la privatisation des données. Une autre solution existe aussi : __variable__. Dans ce cas, la variable est absolument privée et rappellera les méthodes par défaut comme __init__, __del__… En fait, __variable ne produit une variable que partiellement variable qui est bien interdite d'accès hors du contexte mais qui peut toujours être appelée par le biais du chemin complet : instance._classe__variable.
V-K. Paramètres d'initialisation d'instances▲
Il ne sera pas rare, puisque c'est partiellement l'objet de la structuration en objet, d'avoir besoin de créer plusieurs instances de la même classe.
Cela est réalisable très simplement, en passant des paramètres à notre constructeur :
class
MaClasse:
def
__init__
(
self, nom):
self.__essai=
"Bonjour"
self.essai=
"le monde,"
self.nom=
nom
def
affiche
(
self):
print
self.__essai, self.essai, self.nom
objet=
MaClasse
(
"Cedric"
)
objet.affiche
(
)
Nous avons déclaré le paramètre nom puis l'avons associé à self.nom pour le réutiliser plus librement.
Le résultat sera donc :
Bonjour le monde, Cedric
On passera en paramètre de constructeurs les éléments réellement indispensables à l'utilisation et à l'identification de cet objet dans le programme. Pour les paramètres moins fondamentaux, ils pourront être passés à l'appel d'une méthode. Il faudra bien sûr que celle-ci l'accepte au préalable :
class
MaClasse:
def
__init__
(
self, nom):
self.__essai=
"Bonjour"
self.essai=
"le monde,"
self.nom=
nom
def
affiche
(
self, sexe):
print
self.__essai, self.essai, sexe, self.nom
objet=
MaClasse
(
"Cedric"
)
objet.affiche
(
"Monsieur"
)
> Bonjour le monde, Monsieur Cedric
V-L. Étendre ou dériver une classe▲
Un autre cas de réutilisation qui ne sera pas rare, sera souvent d'avoir besoin de créer une variante de l'objet, avec plus de méthodes, des attributs différents ou avec d'autres différences. Dans ce cas, vous avez deux solutions :
- soit utiliser la classe de base telle quelle et ne rien changer (cas lorsqu'on utilise des modules) ;
- soit modifier la classe d'origine pour définir les nouveaux besoins et les y intégrer ;
- soit créer une nouvelle classe qui sera basée sur la première mais qui prendra en compte spécifiquement ces différences.
Vous comprendrez à nous lire que cette dernière méthode est la plus fiable : elle permet de préserver une classe originale saine et exempte des cas particuliers qui sont alors simplement définis dans les classes filles.
Ce concept d'héritage est mis en œuvre simplement en passant le nom de la classe d'origine en paramètre de la classe fille :
class
MaClasse: def
affiche
(
self):
print
self.nom
objet=
MaClasseFille
(
"Cedric"
)
objet.affiche
(
)
def
__init__
(
self, nom):
self.essai=
"Bonjour"
self.nom=
nom
def
affiche
(
self):
print
self.essai, self.nom
class
MaClasseFille
(
MaClasse):
def
affiche
(
self):
print
self.nom
objet=
MaClasseFille
(
"Cedric"
)
objet.affiche
(
)
ce qui affiche
Bonjour Cedric
La classe file peut donc posséder ses propres méthodes, voire redéfinir les méthodes de sa classe mère :
class
MaClasse:
def
__init__
(
self, nom):
self.essai=
"Bonjour"
self.nom=
nom
def
affiche
(
self):
print
self.essai, self.nom
class
MaClasseFille
(
MaClasse):
def
affiche
(
self):
print
self.nom
objet=
MaClasseFille
(
"Cedric"
)
objet.affiche
(
)
Avec cet exemple, la classe fille n'affichera que le nom passé en paramètre lors de l'instanciation alors que dans la classe mère, le nom était concaténé à « Bonjour ». Il sera ainsi possible de dériver énormément de chose sans toucher à l'original et ainsi diminuer le risque d'introduction de bogues dans d'autres applications qui utiliseraient aussi cette classe. Le code devient alors réellement plus simple à maintenir et chaque programme devient plus sûr. Les efforts peuvent être mutualisés sur plusieurs classes, voire plusieurs applications, car toute modification effectuée sur la classe mère sera automatiquement reportée sur les classes filles qui en hérite, ce qui est fondamental lors d'un débogage.
V-M. Les modules : partager des fonctions et objets entre programmes▲
Un module est une classe ou ensemble de classes qui sera spécifiquement utilisé comme ressources dans d'autres programmes. Il a en général été conçu comme tel et s'avère très pratique lors de tâches répétitives.
La règle veut que les modules portent le même nom que la classe qu'ils contiennent. Ainsi MaClasse.py serait le nom de module courant pour le fichier contenant la classe MaClasse. Bien sûr, le module pouvant en contenir plusieurs, cette règle est adaptable.
Au niveau du fichier contenant les classes il est fréquent d'ajouter une condition de test d'exécution (en général en fin de fichier) :
if
__name__
==
"__main__"
pass
# autres instructions au lieu de pass
Le but de cette condition est de définir ce qui doit être exécuté si le module est exécuté comme corps principal du programme, sinon, cette partie est simplement ignorée.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class
MaClasse:
def
__init__
(
self, nom):
self.essai=
"Bonjour"
self.nom=
nom
def
affiche
(
self):
print
self.essai, self.nom
class
MaClasseFille
(
MaClasse):
def
affiche
(
self):
print
self.nom
if
__name__
==
"__main__"
:
pass
Enregistrez ce fichier avec l'extension .py.
V-M-1. Utilisation du module▲
Une fois votre module créé, vous pourrez alors l'importer pour le réutiliser dans tous vos projets :
import
MaClasse
objet =
MaClasse.MaClasseFille
(
"cedric"
)
objet.affiche
(
)
Commentons un peu ce qui se passe ici :
- on commence par importer le module on utilisant son nom, sans l'extension. Par défaut, Python cherche dans le PYTHONPATH et dans le répertoire courant ;
- on instancie les objets en utilisant le nom du module puis le nom de la classe (ce qui n'était pas nécessaire jusqu'ici) ;
- enfin, l'instance peut alors jouer son rôle normalement.
La méthode import va charger l'ensemble du fichier en vue de son exploitation ultérieure.
from
param_classe import
MaClasseFille
objet =
MaClasseFille
(
"Cedric"
)
objet.affiche
(
)
> Cedric
Si le gain ici n'est pas très important à cause de la simplicité de notre fichier module, remarquez qu'avec cette façon de faire, il n'est pas nécessaire de nommer le module lors de l'instanciation. Si cela a l'avantage de faire saisir un peu moins de texte, si vous devez charger plusieurs modules, il faudra être certain qu'il n'y aura pas de conflit dans le nom des classes et méthodes utilisées, problème qui est résolu avec l'utilisation d'import seul et la préservation de son espace de nom.