5.6. Méthodes de classe spéciales

En plus des méthodes de classe ordinaires, il y a un certain nombre de méthodes spéciales que les classes Python peuvent définir. Au lieu d'être appelées directement par votre code (comme les méthodes ordinaires) les méthodes spéciales sont appelées pour vous par Python dans des circonstances particulières ou quand une syntaxe spécifique est utilisée.

Comme vous l'avez vu dans la section précédente, les méthodes ordinaires nous ont permis en grande partie d'envelopper un dictionnaire dans une classe. Mais les méthodes ordinaires seules ne suffisent pas parce qu'il y a beaucoup de choses que vous pouvez faire avec un dictionnaire en dehors d'appeler ses méthodes. Pour commencer vous pouvez lire (get) et écrire (set) des éléments à l'aide d'une syntaxe qui ne fait pas explicitement appel à des méthodes. C'est là que les méthodes de classe spéciales interviennent : elle fournissent un moyen de faire correspondre la syntaxe n'appelant pas de méthodes à des appels de méthodes.

5.6.1. Lire et écrire des éléments

Exemple 5.12. La méthode spéciale __getitem__

    def __getitem__(self, key): return self.data[key]
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
>>> f
{'name':'/music/_singles/kairo.mp3'}
>>> f.__getitem__("name") 1
'/music/_singles/kairo.mp3'
>>> f["name"]             2
'/music/_singles/kairo.mp3'
1 La méthode spéciale __getitem__ à l'air simple. Comme les méthodes ordinaires clear, keys et values, elle ne fait que rediriger vers le dictionnaire pour obtenir sa valeur. Mais comment est-elle appelée ? Vous pouvez appeler __getitem__ directement, mais en pratique vous ne le ferez pas, je le fais ici seulement pour vous montrer comment ça marche. La bonne manière d'utiliser __getitem__ est d'obtenir de Python qu'il fasse l'appel pour vous.
2 Cela à l'apparence exacte de la syntaxe que l'on utilise pour obtenir une valeur d'un dictionnaire et cela retourne bien la valeur que l'on attend. Mais il y a un chaînon manquant : en coulisse, Python convertit cette syntaxe en un appel de méthode f.__getitem__("name"). C'est pourquoi __getitem__ est une méthode de classe spéciale, non seulement vous pouvez l'appeler vous-même, mais vous pouvez faire en sorte que Python l'appelle pour vous grâce à la syntaxe appropriée.

Bien sûr, Python a une méthode spéciale __setitem__ pour accompagner __getitem__, comme nous le montrons dans l'exemple suivant.

Exemple 5.13. La méthode spéciale __setitem__

    def __setitem__(self, key, item): self.data[key] = item
>>> f
{'name':'/music/_singles/kairo.mp3'}
>>> f.__setitem__("genre", 31) 1
>>> f
{'name':'/music/_singles/kairo.mp3', 'genre':31}
>>> f["genre"] = 32            2
>>> f
{'name':'/music/_singles/kairo.mp3', 'genre':32}
1 Comme la méthode __getitem__, __setitem__ redirige simplement l'appel au véritable dictionnaire self.data. Et comme pour __getitem__, vous n'appelez pas directement cette méthode en général, Python appelle __setitem__ pour vous lorsque vous utilisez la bonne syntaxe.
2 Cela ressemble à la syntaxe habituelle d'utilisation d'un dictionnaire, mais en fait f est une classe faisant de son mieux pour passer pour un dictionnaire et __setitem__ est un élément essentiel de cette apparence. Cette ligne de code appelle en fait f.__setitem__("genre", 32) en coulisse.

__setitem__ est une méthode de classe spéciale car elle est appelée pour vous, mais c'est quand même une méthode de classe. Nous pouvons la redéfinir dans une classe descendante tout aussi facilement qu'elle a été définie dans UserDict. Cela nous permet de définir des classes qui se comportent en partie comme des dictionnaires, mais qui ont leur propre comportement dépassant le cadre d'un simple dictionnaire.

Ce concept est à la base de tout le framework que nous étudions dans ce chapitre. Chaque type de fichier peut avoir une classe de manipulation qui sait comment obtenir des méta-données d'un type particulier de fichier. Une fois certains attributs (comme le nom et l'emplacement du fichier) connus, la classe de manipulation sait comment obtenir les autres attributs automatiquement. Cela se fait en redéfinissant la méthode __setitem__, en cherchant des clés particulières et en ajoutant un traitement supplémentaire quand elles sont trouvées.

Par exemple, MP3FileInfo est un descendant de FileInfo. Quand le nom (name) d'un MP3FileInfo est défini, cela ne change pas seulement la valeur de la clé name (comme pour l'ancêtre FileInfo), mais déclenche la recherche de balises MP3 et définit tout un ensemble de clés.

Exemple 5.14. Redéfinition de __setitem__ dans MP3FileInfo

    def __setitem__(self, key, item):         1
        if key == "name" and item:            2
            self.__parse(item)                3
        FileInfo.__setitem__(self, key, item) 4
1 Notez que notre méthode __setitem__ est définie exactement comme la méthode héritée. C'est important car Python appellera la méthode pour nous et qu'il attend un certain nombre d'arguments (techniquement parlant, les noms des arguments n'ont pas d'importance, seulement leur nombre).
2 Voici le point crucial de toute la classe MP3FileInfo : si nous assignons une valeur à la clé name, alors nous voulons faire quelque chose en plus.
3 Le traitement supplémentaire que nous faisons pour les noms (name) est encapsulé dans la méthode __parse. C'est une autre méthode de classe définie dans MP3FileInfo et quand nous l'appelons nous la qualifions avec self. Un appel à __parse tout court chercherait une fonction ordinaire définie hors de la classe, ce qui n'est pas ce que nous voulons, appeler self.__parse cherchera une méthode définie dans la classe. Cela n'a rien de nouveau, c'est de la même manière que l'on fait référence aux données attributs.
4 Après avoir fait notre traitement supplémentaire, nous voulons appeler la méthode héritée. Rappelez-vous que Python ne le fait jamais pour vous, vous devez le faire manuellement. Notez que nous appelons l'ancêtre immédiat, FileInfo, même si il n'a pas de méthode __setitem__. Cela fonctionne parce que Python va remonter la hierarchie d'heritage jusqu'à ce qu'il trouve une classe avec la méthode que nous appelons, cette ligne finira donc par trouver et appeler la méthode __setitem__ définie dans UserDict.
NOTE
Lorsque vous accédez à des données attributs dans une classe, vous devez qualifier le nom de l'attribut : self.attribute. Lorsque vous appelez d'autres méthodes dans une classe, vous devez qualifier le nom de la méthode : self.method.

Exemple 5.15. Setting an MP3FileInfo's name

>>> import fileinfo
>>> mp3file = fileinfo.MP3FileInfo()                   1
>>> mp3file
{'name':None}
>>> mp3file["name"] = "/music/_singles/kairo.mp3"      2
>>> mp3file
{'album': 'Rave Mix', 'artist': '***DJ MARY-JANE***', 'genre': 31,
'title': 'KAIRO****THE BEST GOA', 'name': '/music/_singles/kairo.mp3',
'year': '2000', 'comment': 'http://mp3.com/DJMARYJANE'}
>>> mp3file["name"] = "/music/_singles/sidewinder.mp3" 3
>>> mp3file
{'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder', 
'name': '/music/_singles/sidewinder.mp3', 'year': '2000', 
'comment': 'http://mp3.com/cynicproject'}
1 D'abord nous créons une instance de MP3FileInfo sans lui passer de nom de fichier (nous pouvons le faire parce que l'argument filename de la méthode __init__ est optionnel). Comme MP3FileInfo n'a pas de méthode __init__ propre, Python remonte la hierarchie d'héritage et trouve la méthode __init__ de FileInfo. Cette méthode __init__ appelle manuellement la méthode __init__ de UserDict puis définit la clé name à la valeur de filename, qui est None puisque nous n'avons passé aucun nom de fichier. Donc mp3file est au début un dictionnaire avec une clé, name, dont la valeur est None.
2 Maintenant les choses sérieuses commencent. Définir la clé name de mp3file déclenche la méthode __setitem__ de MP3FileInfo (pas UserDict), qui remarque que nous définissons la clé name avec une valeur réelle et appelle self.__parse. Bien que nous n'ayons pas encore vu le contenu de la méthode __parse, vous pouvez voir à partir de la sortie qu'elle définit plusieurs autres clés : album, artist, genre, title, year et comment.
3 Modifier la clé name recommencera le même processus : Python appelle __setitem__, qui appelle self.__parse, qui définit toutes les autres clés.