Comme vous l'avez vu, FileInfo est une
classe qui se comporte comme un dictionnaire. Pour voir ça plus en
profondeur, regardons la classe UserDict dans
le module UserDict, qui est
l'ancêtre de notre classe FileInfo. Cela n'a
rien de spécial, la classe est écrite en
Python et stockée dans un fichier
.py, tout comme notre code. En fait, elle est
stockée dans le répertoire lib
de votre installation Python.
Dans l'IDEActivePython sous Windows, vous pouvez ouvrir rapidement n'importe quel module dans votre chemin de bibliothèques avec
File->Locate... (Ctrl-L).
Exemple 5.9. Definition de la classe UserDict
class UserDict: def __init__(self, dict=None):
self.data = {} if dict isnot None: self.update(dict)
Notez que UserDict est une classe de
base, elle n'hérite d'aucune classe.
Voici la méthode __init__ que nous
avons redéfini dans la
classe FileInfo. Notez que la liste
d'arguments dans cette classe ancêtre est différente de celle du
descendant. Cela ne pose pas de problème, chaque classe dérivée
peut avoir sa propre liste d'arguments, tant qu'elle appelle la
méthode de l'ancêtre avec les arguments corrects. Ici, la classe
ancêtre a un moyen de définir des valeurs initiales (en passant un
dictionnaire à l'argument dict) ce que notre
FileInfo n'exploite pas.
Python supporte les données
attributs (appelés «variables d'instance» en
Java et
Powerbuilder, «variables
membres» en
C++), qui sont des
données propres à une instance spécifique de la classe. Dans ce
cas, chaque instance de UserDict aura un
attribut de données data. Pour référencer cet
attribut depuis du code extérieur à la classe, vous devez le
qualifier avec le nom de l'instance,
instance.data, de la
même manière que vous qualifiez une fonction avec son nom de
module. Pour référencer un attribut de données depuis la classe,
nous utilisons self pour le qualifier. Par
convention, tous les données attributs sont initalisées à des
valeurs raisonnables dans la méthode
__init__. Cependant, ce n'est pas
obligatoire, puisque les données attributs, comme les variables
locales viennent à
existence lorsqu'on leur assigne une valeur pour la
première fois.
La méthode update est un duplicateur de
dictionnaire. Elle copie toutes les clés et valeurs d'un
dictionnaire à l'autre. Cela n'efface pas le
dictionnaire de destination si il a déjà des clés, celles qui sont
présente dans le dictionnaire source seront récrites, mais les
autres ne seront pas touchées. Considérez
update comme une fonction de fusion, pas de
copie.
Voici une syntaxe que vous n'avez peut-être pas vu
auparavant (je ne l'ai pas employé dans les exemples de ce livre).
C'est une instruction if, mais au lieu d'avoir
un bloc indenté commençant à la ligne suivante, il y a juste une
instruction unique sur la même ligne après les deux points.
C'est une syntaxe tout à fait légale, c'est juste un raccourci
lorsque vous n'avez qu'une instruction dans un bloc (comme
donner une instruction unique sans accolades en C++). Vous pouvez
employer cette syntaxe ou vous pouvez avoir du code indenté sur
les lignes suivantes, mais vous ne pouvez pas mélanger les deux
dans le même bloc.
Java et
Powerbuilder supportent la surcharge de
fonction par liste d'arguments : une classe peut avoir différentes
méthodes avec le même nom mais avec un nombre différent d'arguments
ou des arguments de type différent. D'autres langages (notamment
PL/SQL) supportent même la surcharge de fonction
par nom d'argument : une classe peut avoir différentes méthodes avec
le même nom et le même nombre d'arguments du même type mais avec des
noms d'arguments différents. Python ne
supporte ni l'une ni l'autre, il n'a tout simplement aucune forme de
surcharge de fonction. Les méthodes sont définies uniquement par leur
nom et il ne peut y avoir qu'une méthode par classe avec le même nom.
Donc si une classe descendante a une méthode
__init__, elle redéfinit
toujours la méthode __init__
de la classe ancêtre, même si la descendante la définit
avec une liste d'arguments différente. Et la même règle s'applique
pour toutes les autres méthodes.
Guido, l'auteur originel de Python,
explique la redéfinition de méthode de cette manière : «Les classes
dérivées peuvent redéfinir les méthodes de leur classes de base.
Puisque les méthodes n'ont pas de privilèges spéciaux lorsqu'elles
appellent d'autres méthodes du même objet, une méthode d'une classe de
base qui appelle une autre méthode définie dans cette même classe de
base peut en fait se retrouver à appeler une méthode d'une classe
dérivée qui la redéfini (pour les programmeurs C++ cela veut dire
qu'en Python toutes les méthodes sont
virtuelles).» Si cela n'a pas de sens pour vous (personellement, je
m'y perd complètement) vous pouvez ignorer la question. Je me suis
juste dit que je ferais circuler l'information.
Assignez toujours une valeur initiale à toutes les données
attributs d'une instance dans la méthode
__init__. Cela vous épargera des heures de
débogage plus tard, à la poursuite d'exceptions
AttributeError pour cause de référence à des
attributs non-initialisés (et donc non-existants).
clear est une méthode de classe
ordinaire, elle est disponible publiquement et peut être appelée
par n'importe qui. Notez que clear, comme
toutes les méthodes de classe, a pour premier argument
self (rappelez-vous que vous ne mentionnez pas
self lorsque vous appelez la méthode,
Python l'ajoute pour vous). Notez aussi
la technique de base employé par cette classe enveloppe : utiliser
un véritable dictionnaire (data) comme données
attributs, définir toutes les méthodes d'un véritable dictionnaire
et rediriger chaque méthode vers la méthode du véritable
dictionnaire (au cas où vous l'auriez oublié, la méthode
clear d'un dictionnaire supprime toutes ses clés et
leurs valeurs associées).
La méthode copy d'un
véritable dictionnaire retourne un nouveau dictionnaire qui est un
double exact de l'original (avec les mêmes paires clé-valeur).
Mais UserDict ne peut pas simplement
rediriger la méthode vers self.data.copy, car
cette méthode retourne un véritable dictionnaire, alors que nous
voulons retourner une nouvelle instance qui soit de la même classe
que self.
Nous utilisons l'attribut __class__ pour
voir si self est un
UserDict et, dans ce cas, tout va bien
puisque nous savons comment copier un
UserDict : il suffit de créer un nouveau
UserDict et de lui passer le dictionnaire
véritable qu'est self.data.
Si
self.__class__
n'est pas un UserDict, alors
self doit être une classe dérivée de
UserDict (par exemple
FileInfo), dans ce cas c'est plus
compliqué. UserDict ne sait pas comment
faire une copie exacte d'un de ses descendants. Il pourrait y
avoir, par exemple, d'autres données attributs définies dans la
classe dérivée, ce qui nécessiterait de les copier tous.
Heureusement Python est fourni avec un
module qui remplit cette tâche, le module copy. Je ne vais pas entrer ici dans
les détails (bien que ce soit très intéressant et vaille la peine
que vous y jetiez un coup d'il). Il suffit de dire que copy peut copier un objet
Python quelconque et que c'est comme
cela que nous l'employons ici.
Le reste des méthodes est sans difficulté, les appels sont
redirigés vers les méthodes de self.data.
Dans les versions de Python
antérieures à la 2.2, vous ne pouviez pas directement dériver les
types de données prédéfinis comme les chaînes, les listes et les
dictionnaires. Pour compenser cela, Python
est fourni avec des classes enveloppes qui reproduisent le
comportement de ces types de données prédéfinis :
UserString, UserList et
UserDict. En utilisant un mélange de méthodes
ordinaires et spéciales, la classe UserDict
fait une excellente imitation d'un dictionnaire, mais c'est juste une
classe comme les autres, vous pouvez donc la dériver pour créer des
classes personalisées semblables à un dictionnaire comme
FileInfo. En Python
2.2 et suivant, vous pourriez récrire l'exemple de ce chapitre de
manière à ce que FileInfo hérite directement de
dict au lieu de
UserDict. Cependant, vous devriez quand même
lire l'explication du fonctionnement de
UserDict au cas où vous auriez besoin
d'implémenter ce genre d'objet enveloppe ou au cas où vous auriez à
travailler avec une version de Python antérieure à la 2.2.
En Python il est possible de dériver une classe directement du type de données prédéfini dict, comme dans l'exemple suivant. Il y a trois différence avec la version dérivée de UserDict.
Exemple 5.11. Dériver une classe directement du type prédéfini dict
class FileInfo(dict): "store file metadata"def __init__(self, filename=None):
self["name"] = filename
La première différence est que nous n'avons pas besoin d'importer le module UserDict, puisque dict est un type prédéfini et donc toujours disponible. La seconde est que nous dérivons notre classe de dict directement et non de UserDict.UserDict.
La troisième différence est subtile mais importante. À cause de la manière dont UserDict fonctionne en interne, nous devons appeler explicitement sa méthode __init__ pour l'initialiser correctement. dict ne fonctionne pas de la même manière, ce n'est pas une enveloppe et il ne demande pas d'initialisation explicite.