IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Plongez au coeur de Python ,De débutant à expert


précédentsommairesuivant

V. Les objets et l'orienté objet

Ce chapitre, comme la plupart de ceux qui le suivent, a trait à la programmation orientée objet en Python.

V-A. Plonger

Voici un programme Python complet et fonctionnel. Lisez les doc string du module, des classes et des fonctions pour avoir un aperçu de ce que ce programme fait et de son fonctionnement. Comme d'habitude ne vous inquiétez pas de ce que vous ne comprenez pas, la suite du chapitre est là pour vous l'expliquer.

Exemple 5.1. fileinfo.py

Si vous ne l'avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.

 
Sélectionnez
"""Framework for getting filetype-specific metadata.

Instantiate appropriate class with filename.  Returned object acts like a
dictionary, with key-value pairs for each piece of metadata.
    import fileinfo
    info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3")
    print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()])

Or use listDirectory function to get info on all files in a directory.
    for info in fileinfo.listDirectory("/music/ap/", [".mp3"]):
        ...

Framework can be extended by adding classes for particular file types, e.g.
HTMLFileInfo, MPGFileInfo, DOCFileInfo.  Each class is completely responsible for
parsing its files appropriately; see MP3FileInfo for example.
"""
import os
import sys
from UserDict import UserDict

def stripnulls(data):
    "strip whitespace and nulls"
    return data.replace("\00", "").strip()

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

class MP3FileInfo(FileInfo):
    "store ID3v1.0 MP3 tags"
    tagDataMap = {"title"   : (  3,  33, stripnulls),
                  "artist"  : ( 33,  63, stripnulls),
                  "album"   : ( 63,  93, stripnulls),
                  "year"    : ( 93,  97, stripnulls),
                  "comment" : ( 97, 126, stripnulls),
                  "genre"   : (127, 128, ord)}

    def __parse(self, filename):
        "parse ID3v1.0 tags from MP3 file"
        self.clear()
        try:                               
            fsock = open(filename, "rb", 0)
            try:                           
                fsock.seek(-128, 2)        
                tagdata = fsock.read(128)  
            finally:                       
                fsock.close()              
            if tagdata[:3] == "TAG":
                for tag, (start, end, parseFunc) in self.tagDataMap.items():
                    self[tag] = parseFunc(tagdata[start:end])               
        except IOError:                    
            pass                           

    def __setitem__(self, key, item):
        if key == "name" and item:
            self.__parse(item)
        FileInfo.__setitem__(self, key, item)

def listDirectory(directory, fileExtList):                                        
    "get list of file info objects for files of particular extensions"
    fileList = [os.path.normcase(f)
                for f in os.listdir(directory)]           
    fileList = [os.path.join(directory, f) 
               for f in fileList
                if os.path.splitext(f)[1] in fileExtList] 
    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):      
        "get file info class from filename extension"                             
        subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]       
        return hasattr(module, subclass) and getattr(module, subclass) or FileInfo
    return [getFileInfoClass(f)(f) for f in fileList]                             

if __name__ == "__main__":
    for info in listDirectory("/music/_singles/", [".mp3"]): ***1***
        print "\n".join(["%s=%s" % (k, v) for k, v in info.items()])
        print

***1*** La sortie de ce programme dépend des fichiers qui se trouvent sur votre disque dur. Pour avoir une sortie pertinente, vous devrez changer le chemin pour qu'il pointe vers un répertoire de fichiers MP3 sur votre machine.

Voici la sortie que j'ai obtenu sur ma machine. Votre sortie sera différente, sauf si, par une surprenante coïncidence, vous partagez exactement mes goûts musicaux..

 
Sélectionnez
album=
artist=Ghost in the Machine
title=A Time Long Forgotten (Concept
genre=31
name=/music/_singles/a_time_long_forgotten_con.mp3
year=1999
comment=http://mp3.com/ghostmachine

album=Rave Mix
artist=***DJ MARY-JANE***
title=HELLRAISER****Trance from Hell
genre=31
name=/music/_singles/hellraiser.mp3
year=2000
comment=http://mp3.com/DJMARYJANE

album=Rave Mix
artist=***DJ MARY-JANE***
title=KAIRO****THE BEST GOA
genre=31
name=/music/_singles/kairo.mp3
year=2000
comment=http://mp3.com/DJMARYJANE

album=Journeys
artist=Masters of Balance
title=Long Way Home
genre=31
name=/music/_singles/long_way_home1.mp3
year=2000
comment=http://mp3.com/MastersofBalan

album=
artist=The Cynic Project
title=Sidewinder
genre=18
name=/music/_singles/sidewinder.mp3
year=2000
comment=http://mp3.com/cynicproject

album=Digitosis@128k
artist=VXpanded
title=Spinning
genre=255
name=/music/_singles/spinning.mp3
year=2000
comment=http://mp3.com/artists/95/vxp

V-B. Importation de modules avec from module import

Python fournit deux manières d'importer les modules. Les deux sont utiles et vous devez savoir quand les utiliser. Vous avez déjà vu la première, import module, à la Section 2.4, «Tout est objet». La deuxième manière accomplit la même action avec des différences subtiles mais importante dans son fonctionnement.

Voici la syntaxe de base de from module import :

 
Sélectionnez
from UserDict import UserDict

Cela ressemble à la syntaxe import module que vous connaissez, mais avec une différence importante : les attributs et et les méthodes du module importé types sont importés directement dans l'espace de noms local, ils sont donc disponible directement sans devoir les qualifier avec le nom du module. Vous pouvez importer des éléments précis ou utiliser from module import * pour tout importer.

from module import * en Python est comme module import * en Perl. import module en Python est comme require module en Perl.

from module import * en Python est comme import module.* en Java. import module en Python est comme import module en Java.

Exemple 5.2. import module vs. from module import

 
Sélectionnez
>>> import types
>>> types.FunctionType             ***1***
<type 'function'>
>>> FunctionType                   ***2***
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
NameError: There is no variable named 'FunctionType'
>>> from types import FunctionType ***3***
>>> FunctionType                   ***4***
<type 'function'>

***1*** Le module types ne contient aucune méthode, seulement des attributs pour chaque type d'objet Python. Notez que l'attribut FunctionType doit être qualifié avec le nom de module, types. ***2*** FunctionType lui-même n'a pas été défini dans cet espace de noms, il n'existe que dans le contexte de types. ***3*** Cette syntaxe permet l'importation de l'attribut FunctionType du module types directement dans l'espace de noms local. ***4*** Maintenant FunctionType peut être référé directement, sans mentionner types.

Quand faut-il utiliser from module import ?

  • Quand vous devez accéder fréquemment aux attributs et méthodes et que vous ne voulez pas taper le nom de module sans arrêt, utilisez from module import.
  • Quand vous voulez n'importer que certains attributs et méthodes, utilisez from module import.
  • Quand le module contient des attributs ou des fonctions ayant des noms déjà utilisés dans votre module, vous devez utiliser import module pour éviter les conflits de nom.

En dehors de ces cas, c'est une question de style et vous verrez du code Python écrit des deux manières.

Utilisez from module import * avec modération, il rend plus difficile de déterminer l'origine d'une fonction ou d'un attribut, ce qui rend le débogage et la refactorisation plus difficiles.

Pour en savoir plus sur l'importation de module

V-C. Définition de classes

Python est entièrement orienté objet : vous pouvez définir vos propres classes, hériter de vos classes ou des classes prédéfinies et instancier les classes que vous avez défini.

Définir une classe en Python est simple, comme pour les fonctions, il n'y a pas de définition séparée d'interface. Vous définissez simplement la classe et commencez à coder. Une classe Python commence par le mot réservé class suivi du nom de la classe. Techiquement c'est tout ce qui est requis, une classe n'hérite pas obligatoirement d'une autre.

Exemple 5.3. La classe Python la plus simple

 
Sélectionnez
class Loaf: ***1***
    pass    ***2*** ***3***

***1*** Le nom de cette classe est Loaf et elle n'hérite d'aucune autre classe. Chaque mot d'un nom de classe prend habituellement une majuscule, DeCetteManiere, mais c'est une simple convention et pas une nécéssité. ***2*** Cette classe ne définit aucune méthode ni attribut, mais pour respecter la syntaxe, il est nécéssaire d'avoir quelque chose dans la définition, nous utilisons donc pass. C'est un mot réservé de Python qui signifie simplement «circulez, il n'y a rien à voir». C'est une instruction qui ne fait rien et c'est un bon marqueur lorsque vous écrivez un squelette de fonction ou de classe. ***3*** Vous l'aurez sans doute deviné, tout est indenté dans une classe, comme le code d'une fonction, d'une instruction if, d'une boucle for etc. La première ligne non indenté ne fait plus partie de la classe.

L'instruction L'instruction pass de Python est comme une paire d'accolades vide ({}) en Java ou C. de Python est comme une paire d'accolades vide ({}) en Java ou C.

Bien sûr, dans des cas réels la plupart des classes hériteront d'autres classes et elles définiront leurs propres classes, méthodes et attributs. Mais comme vous l'avez vu, il n'y a rien qu'une classe doit absolument avoir en dehors d'un nom. En particulier, les programmeurs C++ s'étonneront sans doute que les classes Python n'aient pas de constructeurs et de destructeurs explicites. Les classes Python ont quelque chose de semblable à un constructeur : la méthode __init__.

Exemple 5.4. Définition de la classe FileInfo

 
Sélectionnez
from UserDict import UserDict
class FileInfo(UserDict): ***1***

***1*** En Python, l'ancêtre d'une classe est simplement indiqué entre parenthèses immédiatement après le nom de la classe. La classe FileInfo est hérite donc de la classe UserDict (qui a été importée du module UserDict). UserDict est une classe qui se comporte comme un dictionnaire, vous permettant pratiquement de dériver le type de données dictionnaire et d'y ajouter votre propre comportement (il y a des classes semblables UserList et UserString qui vous permettent de dériver les listes et les chaînes). Il y a un peu de magie noire derrière tout cela, nous la démystifierons plus loin dans ce chapitre lorsque nous explorerons la classe UserDict plus en détail.

En Python, l'ancêtre d'une classe est simplement indiqué entre parenthèses immédiatement après le nom de la classe. Il n'y a pas de mot clé spécifique comme extends en Java.

Python supporte l'héritage multiple. Entre les parenthèses qui suivent le nom de classe, vous pouvez indiquer autant de classes ancêtres que vous le souhaitez, séparées par des virgules.

V-C-1. Initialisation et écriture de classes

Cet exemple montre l'initialisation de la classe FileInfo avec la méthode __init__.

Exemple 5.5. Initialisation de la classe FileInfo
 
Sélectionnez
class FileInfo(UserDict):
    "store file metadata"              ***1***
    def __init__(self, filename=None): ***2*** ***3*** ***4***

***1*** Les classes peuvent aussi (et le devraient) avoir une doc string, comme les modules et les fonctions.
***2*** __init__ est appelé immédiatement après qu'une instance de la classe est créée. Il serait tentant mais incorrect de l'appeler le constructeur de la classe. Tentant, parceque ça ressemble à un constructeur (par convention, __init__ est la première méthode définie de la classe), ça se comporte comme un constructeur (c'est le premier morceau de code exécuté dans une nouvelle instance de la classe) et que ça sonne pareil («init» fait penser à quelque chose comme un constructeur). Incorrect, parce qu'au moment ou __init__ est appelé, l'objet à déjà été créé et qu vous avez déjà une référence valide à la nouvelle instance de la classe. Mais __init__ est ce qui se rapproche le plus d'un constructeur en Python et remplit en gros le même rôle.
**3*** Le premier argument de chaque méthode de classe, y compris __init__, est toujours une référence à l'instance actuelle de la classe. Par convention, cet argument est toujours nommé self. Dans la méthode __init__, self fait référence à l'objet nouvellement créé, dans les autres méthodes de classe, il fait référence à l'instance dont la méthode a été appelée. Bien que vous deviez spécifier self explicitement lorsque vous définissez la méthode, vous ne devez pas le spécifier lorsque vous appelez la méthode, Python l'ajoutera pour vous automatiquement.
***4*** Une méthode __init__ peut prendre n'importe quel nombre d'arguments et tout comme pour les fonctions, les arguments peuvent être définis avec des valeurs par défaut, ce qui les rend optionnels lors de l'appel. Ici filename a une valeur par défaut de None, la valeur nulle de Python.

Par convention, le premier argument d'une méthode de classe (la référence à l'instance en cours) est appelé self. Cet argument remplit le rôle du mot réservé this en C++ ou Java, mais self n'est pas un mot réservé de Python, seulement une convention de nommage. Cependant, veuillez ne pas l'appeler autre chose que self, c'est une très forte convention.

Exemple 5.6. Ecriture de la classe FileInfo
 
Sélectionnez
class FileInfo(UserDict):
	"store file metadata"
	def __init__(self, filename=None):
		UserDict.__init__(self)        ***1***
		self["name"] = filename        ***2***
									   ***3***

***1*** Certain langage pseudo-orientés objet comme Powerbuilder ont un concept d'«extension» des constructeurs et autres évènements, dans lequel la méthode de l'ancêtre est appelée automatiquement avant que la méthode du descendant soit exécutée. Python n'a pas ce comportement, vous devez appeler la méthode appropriée de l'ancêtre explicitement. ***2*** Je vous ai dit que cette classe se comportait comme un dictionnaire, en voici le premier signe. Nous assignons l'argument filename comme valeur de la clé name de cet objet. ***3*** Notez que la méthode __init__ ne retourne jamais de valeur.

V-C-2. Quand utiliser self et __init__

Lorsque vous définissez vos méthodes de classe, vous devez indiquer explicitement self comme premier argument de chaque méthode, y compris __init__. Quand vous appelez une méthode d'une classe ancêtre depuis votre classe, vous devez inclure l'argument self. Mais quand vous appelez votre méthode de classe de l'extérieur, vous ne spécifiez rien pour l'argument self, vous l'omettez complètement et Python ajoute automatiquement la référence d'instance. Je me rends bien compte qu'on s'y perd au début, ce n'est pas réellement incohérent même si cela peut sembler l'être car cela est basé sur une distinction (entre méthode liée et non liée) que vous ne connaissez pas pour l'instant.

Ouf. Je sais bien que ça fait beaucoup à absorber, mais vous ne tarderez pas à comprendre tout ça. Toutes les classes Python fonctionnent de la même manière, donc quand vous en avez appris une, vous les connaissez toutes. Mais même si vous oubliez tout le reste souvenez vous de ça, car ça vous jouera des tours :

Les méthodes __init__ sont optionnelles, mais quand vous en définissez une, vous devez vous rappeler d'appeler explicitement la méthode __init__ de l'ancêtre de la classe. C'est une règle plus générale : quand un descendant veut étendre le comportement d'un ancêtre, la méthode du descendant doit appeler la méthode de l'ancêtre explicitement au moment approprié, avec les arguments appropriés.

Pour en savoir plus sur les classes Python

V-D. Instantiation de classes

L'instanciation de classes en Python est simple et directe. Pour instancier une classe, appelez simplement la classe comme si elle était une fonction, en lui passant les arguments que la méthode __init__ définit. La valeur de retour sera l'objet nouvellement créé.

Exemple 5.7. Création d'une instance de FileInfo

 
Sélectionnez
>>> import fileinfo
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3") ***1***
>>> f.__class__                                        ***2***
<class fileinfo.FileInfo at 010EC204>
>>> f.__doc__                                          ***3***
'store file metadata'
>>> f                                                  ***4***
{'name': '/music/_singles/kairo.mp3'}

***1*** Nous créons une instance de la classe FileInfo (définie dans le module fileinfo) et assignons l'instance nouvellement créée à la variable f. Nous passons un paramètre, /music/_singles/kairo.mp3, qui sera l'argument filename de la méthode __init__ de FileInfo. ***2*** Chaque instance de classe à un attribut prédéfini, __class__, qui est la classe de l'objet (notez que la représentation de cet attribut comprend l'adresse physique de l'instance sur ma machine, votre sortie sera différente). Les programmeurs Java sont sans doute familiers de la classe Class, qui contient des méthodes comme getName et getSuperclass permettant d'obtenir les métadonnées d'un objet. En Python, ce type de métadonnées est accessible directement par le biais de l'objet lui-même à travers des attributs comme __class__, __name__ et __bases__. ***3*** Vous pouvez accéder à la doc string de l'instance comme pour une fonction ou un module. Toutes les instances d'une classe partagent la même doc string. ***4*** Rappelez vous quand la méthode __init__ a assigné son argument filename à self["name"]. Voici le résultat. Les arguments que nous passons lorsque nous créons une instance de classe sont envoyés directement à la méthode __init__ (en même temps que la référence à l'objet, self, que Python ajoute automatiquement).

En Python, vous appelez simplement une classe comme si c'était une fonction pour créer une nouvelle instance de la classe. Il n'y a pas d'opérateur new explicite comme pour C++ ou Java.

V-D-1. Ramasse-miettes

Si créer des instances est simple, les détruire est encore plus simple. En général, il n'y a pas besoin de libérer explicitement les instances, elles sont libérées automatiquement lorsque les variables auxquelles elles sont assignées sont hors de portée. Les fuites mémoire sont rares en Python.

Exemple 5.8. Tentative d'implémentation d'une fuite mémoire
 
Sélectionnez
>>> def leakmem():
...     f = fileinfo.FileInfo('/music/_singles/kairo.mp3') ***1***
...     
>>> for i in range(100):
...     leakmem()   ***2***

***1*** A chaque fois que la fonction leakmem est appelée, nous créons une instance de FileInfo et l'assignons à la variable f, qui est une variable locale à la fonction. La fonction s'achève sans jamais libérer f, vous pourriez donc vous attendre à une fuite mémoire, mais vous auriez tort. Lorsque la fonction se termine, la variable locale f est hors de portée. A ce moment, il n'y a plus de référence à l'instance nouvellement créée de FileInfo (puisque nous ne l'avons jamais assignée à autre chose qu'à f), Python détruit alors l'instance pour nous. ***2*** Peu importe le nombre de fois que nous appelons la fonction leakmem, elle ne provoquera jamais de fuite mémoire puisque Python va détruire à chaque fois la nouvelle instance de la classe FileInfo avant le retour de leakmem.

Le terme technique pour cette forme de ramasse-miettes est «comptage de référence». Python maintien une liste des références à chaque instance créée. Dans l'exemple ci-dessus, il n'y avait qu'une référence à l'instance de FileInfo : la variable locale f. Quand la fonction se termine, la variable f sort de la portée, le compteur de référence descend alors à 0 et Python détruit l'instance automatiquement.

Dans des versions précédentes de Python, il y avait des situations où le comptage de référence échouait et Python ne pouvait pas nettoyer derrière vous. Si vous créiez deux instances qui se référençaient mutuellement (par exemple une liste doublement chaînée où chaque noeud a un pointeur vers le noeud prochain et le précédent dans la liste), aucune des deux instances n'était jamais détruite car Python pensait (correctement) qu'il y avait toujours une référence à chaque instance. Python 2.0 a une forme additionnele de ramasse-miettes appelée «mark-and-sweep» qui est assez intelligente pour remarquer ce blocage et nettoyer correctement les références circulaires.

En tant qu'ancien étudiant en Philosophie, cela me dérange de penser que les choses disparaissent quand personne ne les regarde, mais c'est exactement ce qui se passe en Python. En général, vous pouvez simplement ignorer la gestion mémoire et laisser Python nettoyer derrière vous.

Pour en savoir plus sur le ramasse-miettes

V-E. UserDict : une classe enveloppe

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'IDE ActivePython 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

 
Sélectionnez
class UserDict:                                ***1***
    def __init__(self, dict=None):             ***2***
        self.data = {}                         ***3***
        if dict is not None: self.update(dict) ***4*** ***5***

***1*** Notez que UserDict est une classe de base, elle n'hérite d'aucune classe.
***2*** 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.
***3*** 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.
***4*** 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.
***5*** 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).

Exemple 5.10. Méthodes ordinaires de UserDict

 
Sélectionnez
def clear(self): self.data.clear()          ***1***
    def copy(self):                             ***2***
        if self.__class__ is UserDict:          ***3***
            return UserDict(self.data)         
        import copy                             ***4***
        return copy.copy(self)                 
    def keys(self): return self.data.keys()     ***5***
    def items(self): return self.data.items()  
    def values(self): return self.data.values()

***1*** 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). ***2*** 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. ***3*** 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. ***4*** 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. ***5*** 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

 
Sélectionnez
class FileInfo(dict):                  ***1***
    "store file metadata"
    def __init__(self, filename=None): ***2***
        self["name"] = filename

***1*** 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.
***2*** La troisième différence est subtile mais importante. A` 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.

Pour en savoir plus sur UserDict

V-F. 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.

V-F-1. Lire et écrire des éléments

Exemple 5.12. La méthode spéciale __getitem__
 
Sélectionnez
    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__
 
Sélectionnez
    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
 
Sélectionnez
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.

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
 
Sélectionnez
>>> 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.

V-G. Méthodes spéciales avancées

Il y a d'autres méthodes spéciales que __getitem__ et __setitem__. Certaines vous laissent émuler des fonctionnalité dont vous ignorez encore peut-être tout.

Cet exemple montre certaines des autres méthodes spéciales de UserDict.

Exemple 5.16. D'autres méthodes spéciales dans UserDict

 
Sélectionnez
    def __repr__(self): return repr(self.data)     ***1***
    def __cmp__(self, dict):                       ***2***
        if isinstance(dict, UserDict):            
            return cmp(self.data, dict.data)      
        else:                                     
            return cmp(self.data, dict)           
    def __len__(self): return len(self.data)       ***3***
    def __delitem__(self, key): del self.data[key] ***4***

***1*** __repr__ est une méthode spéciale qui est appelée lorsque vous appelez repr(instance). La fonction repr est une fonction prédéfinie qui retourne une représentation en chaîne d'un objet. Elle fonctionne pour tout objet, pas seulement les instances de classes. En fait, vous êtes déjà familier de repr, même si vous l'ignorez. Dans la fenêtre interactive, lorsque vous tapez juste un nom de variable et faites Entrée, Python utilise repr pour afficher la valeur de la variable. Créez un dictionnaire d avec des données, puis faites print repr(d) pour le voir par vous même. ***2*** __cmp__ est appelé lorsque vous comparez des instances de classe. En général, vous pouvez comparer deux objets Python quels qu'ils soient, pas seulement des instances de classe, en utilisant ==. Il y a des règles qui définissent quand les types de données prédéfinis sont considérés égaux. Par exemple, les dictionnaires sont égaux quand ils ont les mêmes clés et valeurs, les chaînes sont égales quand elles ont la même longueur et contiennent la même séquence de caractères. Pour les instances de classe, vous pouvez définir la méthode __cmp__ et écrire la logique de comparaison vous-même et vous pouvez ensuite utiliser == pour comparer des instances de votre classe, Python appelera votre méthode spéciale __cmp__ pour vous. ***3*** __len__ est appelé lorsque vous appelez len(instance). La fonction len est une fonction prédéfinie qui retourne la longueur d'un objet. Elle fonctionne pour tout objet pour lequel il est envisageable de penser qu'il a une longueur. La len d'une chaîne est son nombre de caractères, la len d'un dictionnaire est son nombre de clés et la len d'une liste ou tuple est son nombre d'éléments. Pour les instances de classe, définissez la méthode __len__ et écrivez le calcul de longueur vous-même, puis appelez len(instance) et Python appelera votre méthode spéciale __len__ pour vous. ***4*** __delitem__ est appelé lorsque vous appelez del instance[key], ce qui, vous vous en rappelez peut-être, est le moyen de supprimer des éléments individuels d'un dictionnaire. Quand vous utilisez del sur une instance de classe, Python appelle la méthode spéciale __delitem__ pour vous.

En Java, vous déterminez si deux variables de chaînes référencent la même zone mémoire à l'aide de str1 == str2. On appelle cela identité des objets et la syntaxe Python en est str1 is str2. Pour comparer des valeurs de chaînes en Java, vous utiliseriez str1.equals(str2), en Python, vous utiliseriez str1 == str2. Les programmeurs Java qui ont appris que le monde était rendu meilleur par le fait que == en Java fasse une comparaison par identité plutôt que par valeur peuvent avoir des difficultés à s'adapter au fait que Python est dépourvu d'un tel piège.

Vous trouvez peut-être que ça fait beaucoup de travail pour faire avec une classe ce qu'on peut faire avec un type de données prédéfini. Et c'est vrai que tout serait plus simple (et la classe UserDict serait inutile) si on pouvait hériter d'un type de données prédéfini comme un dictionnaire. Mais même si vous pouviez le faire, les méthodes spéciales seraient toujours utiles, car elles peuvent être utilisées dans n'importe quelle classe, pas seulement dans une classe enveloppe comme UserDict.

Les méthodes spéciales permettent à toute classe de stocker des paires clé-valeur comme un dictionnaire, simplement en définissant la méthode __setitem__. Toute classe peut se comporter comme une séquence, simplement en définissant la méthode __getitem__. Toute classe qui définit la méthode __cmp__ peut être comparée avec ==. Et si votre classe représente quelque chose qui a une longeur, ne créez pas une méthode GetLength, définissez la méthode __len__ et utilisez len(instance).

Alors que les autres langages orientés objet ne vous laissent définir que le modèle physique d'un objet («cet objet a une méthode GetLength»), les méthodes spéciales de Python comme __len__ vous permettent de définir le modèle logique d'un objet («cet objet a une longueur»).

Il y a de nombreuses autres méthodes spéciales. Un ensemble de ces méthodes permet aux classes de se comporter comme des nombres, permettant l'addition, la soustraction et autres opérations arithmétiques sur des instances de classe (l'exemple type en est une classe représentant les nombres complexes, nombres ayant à la fois un composant réel et imaginaire). La méthode __call__ permet à une classe de se comporter comme une fonction, ce qui permet d'appeler une instance de classe directement. Il y a aussi d'autres méthodes spéciales permettant aux classes d'avoir des données attributs en lecture seule ou en écriture seule, nous en parlerons dans des chapitres à venir.

Pour en savoir plus sur les méthodes de classe spéciales

V-H. Attributs de classe

Vous connaissez déjà les données attributs, qui sont des variables appartenant à une instance particulière d'une classe. Python permet aussi les attributs de classe, qui sont des variables appartenant à la classe elle-même.

Exemple 5.17. Présentation des attributs de classe

 
Sélectionnez
class MP3FileInfo(FileInfo):
    "store ID3v1.0 MP3 tags"
    tagDataMap = {"title"   : (  3,  33, stripnulls),
                  "artist"  : ( 33,  63, stripnulls),
                  "album"   : ( 63,  93, stripnulls),
                  "year"    : ( 93,  97, stripnulls),
                  "comment" : ( 97, 126, stripnulls),
                  "genre"   : (127, 128, ord)}

>>> import fileinfo
>>> fileinfo.MP3FileInfo            ***1***
<class fileinfo.MP3FileInfo at 01257FDC>
>>> fileinfo.MP3FileInfo.tagDataMap ***2***
{'title': (3, 33, <function stripnulls at 0260C8D4>), 
'genre': (127, 128, <built-in function ord>), 
'artist': (33, 63, <function stripnulls at 0260C8D4>), 
'year': (93, 97, <function stripnulls at 0260C8D4>), 
'comment': (97, 126, <function stripnulls at 0260C8D4>), 
'album': (63, 93, <function stripnulls at 0260C8D4>)}
>>> m = fileinfo.MP3FileInfo()      ***3***
>>> m.tagDataMap
{'title': (3, 33, <function stripnulls at 0260C8D4>), 
'genre': (127, 128, <built-in function ord>), 
'artist': (33, 63, <function stripnulls at 0260C8D4>), 
'year': (93, 97, <function stripnulls at 0260C8D4>), 
'comment': (97, 126, <function stripnulls at 0260C8D4>), 
'album': (63, 93, <function stripnulls at 0260C8D4>)}

***1*** MP3FileInfo est la classe elle-même, pas une instance particulière de cette classe.

***2*** tagDataMap est un attribut de classe : littéralement, un attribut de la classe. Il est diponible avant qu'aucune instance de la classe n'ait été créée.

***3*** Les attributs de classe sont disponibles à la fois par référence directe à la classe et par référence à une instance quelconque de la classe.

En Java, les variables statiques (appelées attributs de classe en Python) aussi bien que les variables d'instance (appelées données attributs en Python) sont définies immédiatement après la définition de la classe (avec le mot-clé static pour les premières). En Python, seuls les attributs de classe peuvent être définis à cet endroit, les données attributs sont définies dans la méthode __init__.

Les attributs de classe peuvent être utilisés comme des constantes au niveau de la classe (ce qui est la manière dont nous les utilisons dans MP3FileInfo), mais ils ne sont pas vraiment des constantes. Vous pouvez également les modifier.

Il n'y a pas de constantes en Python. Tout est modifiable en faisant un effort. C'est en accord avec un des principes essentiels de Python : un mauvais comportement doit être découragé mais pas interdit. Si vous voulez vraiment changer la valeur de None, vous pouvez le faire, mais ne venez pas vous plaindre que votre code est impossible à déboguer.

Exemple 5.18. Modification des attributs de classe

 
Sélectionnez
>>> class counter:
...     count = 0                     ***1***
...     def __init__(self):
...         self.__class__.count += 1 ***2***
...     
>>> counter
<class __main__.counter at 010EAECC>
>>> counter.count                     ***3***
0
>>> c = counter()
>>> c.count                           ***4***
1
>>> counter.count
1
>>> d = counter()                     ***5****
>>> d.count
2
>>> c.count
2
>>> counter.count
2

***1*** count est un attribut de la classe counter.

***2*** __class__ est un attribut prédéfini de toute instance de classe (de toute classe). C'est une référence à la classe dont self est une instance (dans ce cas, la classe counter).

***3*** Comme count est un attribut de classe, il est disponible par référence directe à la classe, avant que nous ayions créé une instance de la classe.

***4*** Créer une instance de la classe appelle la méthode __init__, qui incrémente l'attribut de classe count de 1. Cela affecte la classe elle-même, pas seulement l'instance nouvellement créée.

***5*** Créer une seconde instance incrémente à nouveau l'attribut de classe count. Vous constatez que l'attribut de classe est partagé par la classe et toutes ses instances.

V-I. Fonctions privées

Comme la plupart des langages, Python possède le concept d'éléments privées :

  • des fonctions privées, qui ne peuvent être appelées de l'extérieur de leur module ;
  • des méthodes de classe privées, qui ne peuvent être appelées de l'extérieur de leur classe ;
  • des attributs privés, qui ne peuvent être accédé de l'extérieur de leur classe.

Contrairement à la plupart des langages, le caractère privé ou public d'une fonction, d'une méthode ou d'un attribut est déterminé en Python entièrement par son nom.

Si le nom d'une fonction, d'une méthode de classe ou d'un attribut commence par (mais ne se termine pas par) deux caractères de soulignement il s'agit d'un élément privé, tout le reste est public. Python ne possède pas de concept de méthode protégée (accessible seulement à la même classe ou a ses descendants). Les méthodes d'une classe sont ou privées (accessibles seulement à la même classe) ou publiques (accessibles à tout le monde).

MP3FileInfo a deux méthodes : __parse et __setitem__. Comme nous en avons déjà discuté, __setitem__ est une méthode spéciale, vous l'appelez normalement indirectement en utilisant la syntaxe de dictionnaire sur une instance de classe, mais elle est publique et vous pouvez l'appeler directement (même d'en dehors du module fileinfo) si vous avez une bonne raison de le faire. Par contre __parse est privée, car elle a deux caractères de soulignement au début de son nom.

En Python, toutes les méthodes spéciales (comme __setitem__) et les attributs prédéfinis (comme __doc__) suivent une convention standard : il commencent et se terminent par deux caractères de soulignement. Ne nommez pas vos propres méthodes et attributs de cette manière, cela n'apporterait que de la confusion pour vous et les autres.

Exemple 5.19. Tentative d'appel d'une méthode privée

 
Sélectionnez
>>> import fileinfo
	>>> m = fileinfo.MP3FileInfo()
	>>> m.__parse("/music/_singles/kairo.mp3") ****1***
	Traceback (innermost last):
  File "<interactive input>", line 1, in ?
	AttributeError: 'MP3FileInfo' instance has no attribute '__parse'

***1*** Si vous essayez d'appeler une méthode privée, Python lévera une exception un peu trompeuse disant que la méthode n'existe pas. Bien sûr elle existe, mais elle est privée, donc elle n'est pas accessible d'en dehors de la classe. A proprement parler, les méthodes privées sont accessibles d'en dehors de leur classe, mais pas facilement accessibles. Rien n'est vraiment privé en Python, en interne les noms des méthodes et attributs privés sont camouflés à la volée pour les rendre inaccessibles par leur nom d'origine. Vous pouvez accéder à la méthode __parse de la classe MP3FileInfo par le nom _MP3FileInfo__parse. Vous êtes prié de trouver ça intéressant mais de promettre de ne jamais, jamais l'utiliser dans votre code. Les méthodes privées ont une bonne raison de l'être, mais comme beaucoup d'autres choses en Python, leur caractère privé est en dernière instance une question de convention et non d'obligation.

Pour en savoir plus sur les fonctions privées

V-J. Résumé

Voila pour ce qui est des chicanes techniques des objets. Vous verrez une application réelle des méthodes de classe spéciales au Chapitre 12, qui utilise getattr pour créer un mandataire d'un service web distant.

Le prochain chapitre continuera d'utiliser ce programme d'exemple pour explorer d'autres concepts de Python comme les exceptions, les objets-fichiers et les boucles for.

Avant de plonger dans le prochain chapitre, assurez-vous que vous vous sentez à l'aise pour :

  • Importer des modules en utilisant import module ou from module import
  • Definir et instancier des classes
  • Definir des méthodes __init__ et autres méthodes spéciales et comprendre quand elles sont appelées
  • Dériver de UserDict pour définir des classes qui se comportent comme des dictionnaires
  • Définir des données attributs et des attributs de classe et comprendre la différence entre les deux
  • Définir des méthodes privées

précédentsommairesuivant