Sous-sections


9. Classes

Le mécanisme de classe en Python permet d'introduire les classes avec un minimum de syntaxe et sémantique nouvelles. C'est un mélange des mécanismes de classe de C++ et Modula-3. Comme les modules, les classes en Python n'installent pas de barrière absolue entre la définition et l'utilisateur, mais appellent plutôt à la politesse de l'utilisateur pour éviter l'``effraction de la définition''. Les caractéristiques les plus importantes des classes sont pleinement présentes: le mécanisme d'héritage permet la multiplicité des classes de base, une classe dérivée peut surcharger n'importe quelle méthode de sa ou ses classes de base, une méthode peut appeler une méthode de sa classe de base avec le même nom. Les objets peuvent contenir un nombre arbitraire de données privées.

Dans la terminologie du C++, tous les membres d'une classe (dont les données membres) sont publics, et toutes les fonctions membres sont virtuelles. Il n'y a pas de constructeurs ou de destructeurs particuliers. Comme en Modula-3, il n'y a pas de raccourcis pour faire référence aux membres d'un objet à partir de ses méthodes: une méthode est déclarée avec un premier argument explicite qui représente l'objet, qui est fourni implicitement à l'appel. Comme en Smalltalk, les classes sont elles-mêmes des objets, mais dans un sens plus large: en Python, tous les types de données sont des objets. Ceci fournit la sémantique pour l'importation et le renommage. Mais, comme en C++ et en Modula-3, les types intégrés ne peuvent pas être utilisés comme classes de base pour des extensions par l'utilisateur. En plus, comme en C++ mais contrairement à Modula-3, la plupart des opérateurs intégrés qui ont une syntaxe particulière (opérateurs arithmétiques, indiçage, etc.) peuvent être redéfinis pour des instances de classe.


9.1 Un Mot Sur la Terminologie

A défaut d'une terminologie universellement acceptée pour parler des classes, j'utiliserai à l'occasion des termes de Smalltalk et C++. (J'utiliserais volontiers des termes de Modula-3, puisque sa sémantique orientée objet est plus proche de celle de Python que celle de C++, mais j'imagine que peu de lecteurs ont entendu parler de ce langage.)

Je dois aussi vous prévenir qu'il y a un piège terminologique pour les lecteurs familiers de l'orienté objet: le mot ``objet'' en Python ne veut pas forcément dire instance d'une classe. Comme en C++ et Modula-3 et contrairement à Smalltalk, tous les types Python ne sont pas des classes: les types intégrés de base comme les entiers et les listes ne le sont pas, et même des types plus exotiques comme les fichiers ne le sont pas non plus. Pourtant, tous les types en Python partagent un peu de sémantique commune, décrite au mieux par le mot objet.

Les objets possèdent une individualité, et des noms multiples (dans des portées multiples) peuvent être liés au même objet. Cela s'appelle aliasing dans d'autres langages. On ne le remarque pas de prime abord dans Python, et on peut l'ignorer pour le traitement des types de base non modifiables (nombres, chaînes de caractères, tuples). Néanmoins, l'aliasing a un effet (voulu!) sur la sémantique du code Python qui met en jeu des objets modifiables comme listes, dictionnaires et la plupart des types représentant des entités à l'exception du programme (fichiers, fenêtres, etc.). Ceci est mis à profit dans les programmes, puisque les alias se comportent comme des pointeurs à plusieurs points de vue. Par exemple, le passage en paramètre d'un objet n'est pas coûteux puisque seul un pointeur est transmis par l'implémentation; et si une fonction modifie un objet reçu en argument, l'appelant verra la modification -- ce qui élimine le besoin d'avoir deux mécanismes de passage d'arguments comme en Pascal.


9.2 Les Portées et les Espaces de Noms en Python

Avant d'introduire les classes, je dois vous dire quelques mots sur les règles de portée en Python. Les définitions de classe jouent astucieusement avec les espaces de noms, et vous devez savoir comment fonctionnent les portées et les espaces de noms pour comprendre ce qui se passe. En fait, la connaissance de ce sujet est utile à tout programmeur Python avancé.

D'abord quelques définitions.

Un espace de noms (name space) est une relation entre des noms et des objets. La plupart des espaces de noms sont actuellement implémentés comme des dictionnaires, mais cela n'est pas visible (sauf sur les performances peut-être) et pourrait changer dans le futur. Quelques exemples d'espaces de noms: l'ensemble des noms intégrés (les fonctions telles que abs(), et les noms d'exception intégrés); les noms globaux dans un module; les noms locaux au cours d'un appel de fonction. En un sens, l'ensemble des attributs d'un objet constitue aussi un espace de noms. La chose importante à savoir sur les espaces de noms est qu'il n'y a absolument aucune relation entre les noms contenus dans les différents espaces de noms; par exemple, deux modules différents peuvent définir tous les deux une fonction ``maximise'' sans confusion possible -- les utilisateurs des modules doivent préfixer par le nom du module à l'utilisation.

Au passage, j'utilise le mot attribut (attribute) pour n'importe quel nom qui suit un point -- par exemple, dans l'expression z.real, real est un attribut de l'objet z. Strictement parlant, les références à des noms dans des modules sont des attributs; dans l'expression nommod.nomfonc, nommod est un objet module et nomfonc en est un attribut. Dans ce cas, il y a un rapport direct entre les attributs du module et les noms globaux définis dans le module: ils partagent le même espace de noms!9.1

Les attributs peuvent être en lecture seule ou bien modifiables. Dans ce cas, on peut affecter des valeurs à des attributs. Les attributs d'un module sont modifiables: vous pouvez faire "nommod.la_reponse = 42". Les attributs modifiables peuvent aussi être effacés avec l'instruction del, par exemple "del nommod.la_reponse".

Les espaces de noms sont créés à des moments différents et ont des durées de vie différentes. L'espace de noms qui contient les noms intégrés est créé au lancement de l'interpréteur Python, et n'est jamais effacé. L'espace de noms global pour un module est créé quand la définition du module est chargée; normalement, les espaces de noms des modules vivent jusqu'à la mort de l'interpréteur. Les instructions exécutées à l'invocation de l'interpréteur par le niveau supérieur, qu'elles soient lues depuis un fichier ou entrées de façon interactive, sont considérées comme faisant partie d'un module appelé __main__, elles ont donc leur propre espace de noms global. (En fait, les noms intégrés font aussi partie d'un module appelé __builtin__.)

L'espace de noms local à une fonction est créé quand celle-ci est appelée et il est effacé quand la fonction se termine ou déclenche une exception qui n'est pas gérée dans la fonction. (En fait, oublié décrit mieux ce qui se passe vraiment.) Evidemment, les appels récursifs ont chacun leur propre espace de noms.

Une portée (scope) est une région textuelle d'un programme Python dans laquelle un espace de noms est directement accessible. ``Directement accessible'' veut dire qu'une référence non qualifiée à un nom cherchera ce nom dans cet espace de noms.

Bien qu'elles soient déterminées statiquement, les portées sont utilisées dynamiquement. A n'importe quel moment de l'exécution, exactement trois portées imbriquées sont utilisées (c'est-à-dire exactement trois espaces de noms sont accessibles directement): la portée immédiate, qui est explorée en premier, contient les noms locaux, la portée intermédiaire, explorée ensuite, contient les noms globaux du module courant, et la portée extérieure (explorée en dernier) correspond à l'espace de noms contenant les noms intégrés.

Normalement, la portée locale fait référence aux noms de la fonction courante (textuellement). En dehors des fonctions, la portée locale fait référence au même espace de noms que la portée globale: l'espace de noms du module. Les définitions de classe placent encore un autre espace de noms dans la portée locale.

Il est important de voir que les portées sont déterminées de façon textuelle: la portée globale d'une fonction définie dans un module est l'espace de noms de ce module, peu importe d'où ou à travers quel alias cette fonction est appelée. D'un autre côté, la recherche de noms elle-même est effectuée dynamiquement, à l' exécution -- toutefois, la définition du langage tend à évoluer vers la résolution statique de noms, au moment de la ``compilation'', alors ne vous basez pas sur la résolution dynamique de noms! (En fait, les variables locales sont déjà déterminées de façon statique.)

Un point titilleux de Python est que les affectations se font toujours dans la portée immédiate. L'affectation ne copie pas de données -- elle ne fait qu'affecter un nom à un objet. Ceci est vrai aussi de l'effacement: l'instruction "del x" enlève le lien vers x de l'espace de noms référencé par la portée locale. En fait, toute opération qui introduit de nouveaux noms utilise la portée locale: en particulier, les instructions d'importation et les définitions de fonction lient le nom du module ou de la fonction à la portée locale. (L'instruction global peut être utilisée pour indiquer que certaines variables vivent dans la portée globale.)


9.3 Une Première Approche des Classes

Les classes introduisent un peu de syntaxe nouvelle, trois nouveaux types d'objet, et quelques points de sémantique supplémentaires.


9.3.1 Syntaxe de la Définition de Classes

La forme la plus simple de définition de classe ressemble à ceci:

class NomClasse:
    <instruction-1>
    .
    .
    .
    <instruction-N>

Les définitions de classe, comme les définitions de fonction (instructions def) doivent être exécutées pour entrer en effet. (Vous pourriez placer une définition de classe dans une branche d'une instruction if, ou à l'intérieur d'une fonction.)

Dans la pratique, les instructions à l'intérieur d'une définition de classe seront souvent des définitions de fonction, mais d'autres instructions sont acceptées, et parfois s'avèrent utiles -- plus de détails sur le sujet ci-dessous. Les définitions de fonction à l'intérieur d'une classe ont une forme particulière de liste d'arguments, dictée par les conventions d'appel de méthode -- ceci

A l'entrée d'une définition de fonction, un nouvel espace de noms est créé et utilisé comme portée locale -- ainsi, toute affectation de variables rentre dans cet espace de noms. En particulier, les définitions de fonctions y rattachent le nom des nouvelles fonction.

Lorsque la définition de la classe est achevée de façon normale (par la fin), un objet classe (class object) est créé. Il s'agit essentiellemnt d'un enrobage autour du contenu de l'espace de noms créé par la définition de classe; nous verrons davantage de caractéristiques des objets classes dans la section suivante. La portée locale d'origine (celle en cours avant le début de la définition de classe) est réinstallée, et l'objet classe est lié ici au nom donné dans l'en-tête de la définition de classe (NomClasse dans l'exemple).


9.3.2 Objets Classes

Les objets classe admettent deux sortes d'opérations: la référenciation des attributs et l'instanciation.

Les références aux attributs (attribute references) utilisent la syntaxe standard utilisée pour toutes les références d'attribut en Python: obj.nom. Les noms d'attribut valides sont ceux qui étaient dans l'espace de noms de la classe quand l'objet classe a été créé. Donc, si la définition de classe ressemble à:

class MaClasse:
    "Une classe simple pour exemple''
    i = 12345
    def f(x):
        return 'bonjour'

alors MaClasse.i et MaClasse.f sont des références d'attribut valides, qui renvoient un entier et un objet fonction, respectivement. On peut affecter une valeur aux attributs de classe, donc vous pouvez changer la valeur de MaClasse.i par affectation. __doc__ est un attribut valide, en lecture exclusive, qui renvoie la docstring correspondant à la classe: "Une classe simple pour exemple").

L'instantiation de classe utilise la notation d'appel de fonction. Faites comme si l'objet classe était une fonction sans paramètres qui renvoie une instance nouvelle de la classe. Par exemple, (avec la classe précédente):

x = MaClasse()

crée une nouvelle instance de la classe et affecte cet objet à la variable locale x.

L'opération d'instanciation (``appeller'' un objet classe) crée un objet vide. De nombreuses classes aiment bien créer les objets dans un état initial connu. Ainsi une classe peut définir une méthode spéciale nommée __init__(), comme ceci:

    def __init__(self):
        self.donnee = []

Quand une classe définit une méthode __init__(), l'instanciation de la classe appelle automatiquement __init__() pour l'instance de la classe nouvellement créée. Ainsi, dans cet exemple, une instance nouvelle, initialisée, peut être obtenue par:

x = MaClasse()

Bien-sûr, la méthode __init__() peut avoir des arguments pour offrir plus de souplesse. Dans ce cas, les arguments fournis à l'opérateur d'instanciation de la classe sont passés à __init__(). Par exemple,

>>> class Complexe:
...     def __init__(self, partiereelle, partieimaginaire):
...         self.r = partiereelle
...         self.i = partieimaginaire
... 
>>> x = Complexe(3.0,-4.5)
>>> x.r, x.i
(3.0, -4.5)


9.3.3 Objets Instances

Que peut-on faire avec les objets instances? Les seules opérations acceptées par des objets instance sont des références à leurs attributs. Il y a deux sortes de noms d'attributs valides.

J'appellerai la première données attributs (data attributes). Ils correspondent aux ``variables d'instance'' (instance variables) en Smalltalk, et aux ``données membres'' (data members) en C++. Les données attributs n'ont pas besoin d'être déclarées; comme les variables locales, elles apparaissent lorsqu'on leur affecte une valeur pour la première fois. Par exemple, si x est l'instance de MaClasse créée précédemment, le morceau de code suivant affichera la valeur 16, sans laisser de trace:

x.compteur = 1
while x.compteur < 10:
    x.compteur = x.compteur * 2
print x.compteur
del x.compteur

La seconde sorte de référence d'attribut acceptée par les objets instance sont les méthodes (methods). Une méthode est une fonction qui ``appartient'' à un objet. (En Python, le terme méthode n'est pas exclusif aux instances de classe: d'autres types d'objet peuvent avoir des méthodes, par exemple les objets liste ont des méthodes appelées append, insert, remove, sort, etc. Néanmoins, dans ce qui suit, nous allons utiliser le terme méthode pour désigner exclusivement les méthodes d'un objet instance de classe, sauf mention explicite.)

Les noms de méthodes valides pour un objet instance dépendent de sa classe. Par définition, tous les attributs d'une classe qui sont des fonctions (définies par l'utilisateur) définissent des méthodes correspondantes pour les instances. Ainsi, dans notre exemple, x.f est une référence valide à une méthode, puisque MaClasse.f est une fonction, mais x.i ne l'est pas, vu que MaClasse.i ne l'est pas. Toutefois x.f n'est pas la même chose que MaClasse.f -- c'est un objet méthode (method object), et non pas un objet fonction.


9.3.4 Objets Méthodes

D'habitude, une méthode est appelée de façon directe, par exemple:

x.f()

Dans notre exemple, ceci renverrait la chaîne 'salut monde'. Or, il n'est pas nécessaire d'appeler une méthode tout de suite: x.f est un objet méthode, il peut être rangé quelque part et être appelé plus tard, par exemple:

xf = x.f
while 1:
    print xf()

continuera à afficher "bonjour" jusqu'à la fin des temps.

Que se passe-t-il exactement lorsqu'une méthode est appelée? Vous avez peut-être remarqué que x.f() a été appelée sans argument ci-dessus, alors que la définition de fonction pour f en spécifiait un. Qu'est-il arrivé à l'argument? On se doute bien que Python déclenche une exception quand une fonction qui requiert un argument est appelée sans argument -- même si l'argument n'est pas effectivement utilisé...

En fait, vous avez peut-être deviné la réponse: la particularité des méthodes est que l'objet est passé comme premier argument à la fonction. Dans notre exemple, l'appel x.f() est l'équivalent exact de MaClasse.f(x). En général, appeler une méthode avec une liste de n arguments équivaut à appeler la fonction correspondante avec une liste d'arguments qui est le résultat de l'insértion de l'objet avant le premier argument.

Si vous n'avez toujours pas compris comment fonctionnent les méthodes, un regard sur l'implémentation va peut-être clarifier les choses. Lorsqu'un attribut d'une instance est référencé et qu'il n'est pas une donnée attribut, une recherche est entamée dans sa classe. Si le nom correspond à un attribut de classe valide qui est un objet fonction, un objet méthode est créé en empaquetant (des pointeurs sur) l'objet instance et l'objet fonction trouvé dans un objet abstrait: c'est l'objet méthode. Lorsque l'objet méthode est appelé avec une liste d'arguments, il est depaqueté, une nouvelle liste d'arguments est construite à partir de l'objet instance et de la liste d'arguments originelle, puis l'objet fonction est appelé avec cette nouvelle liste d'arguments.


9.4 Quelques Remarques

Les données attributs écrasent les méthodes de même nom; pour éviter des conflits de noms accidentels, qui peuvent causer des bogues difficiles à trouver dans des programmes conséquents, il est sage d'utiliser une convention qui minimise les chances de conflit, par exemple, mettre en majuscules les noms des méthodes, préfixer les noms des données attributs avec une même courte chaîne de caractères (peut-être un simple tiret-bas), ou utiliser des verbes pour les méthodes et des substantifs pour les données.

Les données attributs peuvent être référencées par des méthodes aussi bien que par les utilisateurs ordinaires (``clients'') d'un objet. Autrement dit, les classes ne sont pas utilisables pour implémenter des types abstraits purs. En fait, rien dans Python ne permet d'assurer le secret des données -- tout est basé sur des conventions. (D'un autre côté, l'implémentation de Python, écrite en C, peut cacher complètement les détails d'implémentation et de contrôle d'accès à un objet si besoin est; ceci peut être utilisé par les extensions de Python écrites en C.)

Les clients doivent utiliser les données attributs avec précaution -- ils peuvent bouleverser des invariants entretenus par les méthodes en écrasant leurs données attributs. Notez bien que les clients peuvent rajouter des données attributs de leur cru à un objet instance sans affecter la validité des méthodes, pourvu que les conflits de noms soient évités -- là encore, une convention de nommage peut éviter bien des migraines.

Il n'y a pas de raccourci pour faire référence à des données attributs (ou à d'autres méthodes!) à partir d'une méthode. Je trouve que cela augmente en fait la lisibilité des méthodes: il n'y a aucune chance de confondre les variables locales et les variables d'instance lorsqu'on examine une méthode.

Par convention, le premier argument d'une méthode est souvent appelé self. Ce n'est qu'une convention: le mot self n'a absolument aucun sens spécial pour Python. (Remarquez, tout de même, que si votre code ne suit pas la convention, il peut se révéler moins lisible par d'autres programmeurs Python, et il est aussi concevable qu'un explorateur de classes soit écrit, qui se base sur cette convention.)

Tout objet fonction qui est aussi un attribut d'une classe définit une méthode pour les instances de cette classe. Il n'est pas nécessaire que la définition de la fonction soit textuellement comprise dans la définition de la classe: affecter un objet fonction à une variable locale dans la classe marche aussi. Par exemple:

# Fonction définie en dehors de la classe
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'bonjour'
    h = g

Maintenant, f, g et h sont tous trois des attributs de la classe C qui font référence à des objets fonctions, et par conséquent ils sont tous les trois des méthodes des instances de C -- h équivaut exactement à g. Remarquez que cette pratique ne sert souvent qu'à embrouiller le lecteur du programme.

Les méthodes peuvent appeler d'autres méthodes en utilisant les attributs méthodes de l'argument self, par exemple:

class Sac:
    def vider(self):
        self.donnees = []
    def ajouter(self, x):
        self.donnees.append(x)
    def ajouterdoublon(self, x):
        self.ajouter(x)
        self.ajouter(x)

Les méthodes peuvent faire référence à des noms globaux de la même façon que les fonctions ordinaires. La portée globale associée à une méthode est celle du module qui contient la définition de la classe. (La classe elle-même ne sert jamais de portée globale!) Alors qu'on trouve rarement de bonnes raisons pour utiliser des données globales dans une méthode, il y a plusieurs utilisations légitimes de la portée globale: ne serait-ce que le fait que les fonctions et les modules importés dans la portée globale peuvent être utilisés par les méthodes, de même que les fonctions et les classes qui y sont définies. D'habitude, la classe qui contient la méthode est elle-même définie dans cette portée globale, et dans la section suivante nous allons voir quelques bonnes raisons pour lesquelles une méthode pourrait vouloir faire référence à sa propre classe!


9.5 Héritage

Bien sûr, une caractéristique du langage ne serait pas digne du mot ``classe'' si elle ne permettait pas l'héritage. La syntaxe pour définir une classe dérivée ressemble à ceci:

class NomClasseDerivee(NomClasseDeBase):
    <instruction-1>
    .
    .
    .
    <instruction-N>

Le nom NomClasseDeBase doit être défini dans une portée contenant la définition de la classe dérivée. A la place d'un nom de classe de base, une expression est acceptée. Ceci est utile lorsque la classe de base est définie dans un autre module, par exemple:

class NomClasseDerivee(nommod.NomClasseDeBase):

L'exécution d'une définition de classe dérivée se déroule comme pour une classe de base. Quand l'objet classe est construit, la classe de base est mémorisée. Ceci est employé dans la résolution des références d'attribut: si l'attribut demandé n'est pas trouvé dans la classe, il est recherché dans la classe de base. Cette règle est employée récursivement si la classe de base est elle-même dérivée depuis une autre classe.

Il n'y a rien de spécial dans l'instantiation d'une classe dérivée: NomClasseDerivee() crée une nouvelle instance de la classe. Les références aux méthodes sont résolues ainsi: l'attribut est recherché dans la classe correspondante, en descendant la chaîne des classes de base si besoin est, et la référence à la méthode est valide si cette recherche aboutit à un objet fonction.

Les classe dérivées peuvent redéfinir les méthodes de leurs classes de base. Puisque les méthodes ne jouissent pas de privilèges particuliers lorsqu'elles appellent d'autres méthodes du même objet, la méthode d'une classe de base qui appelle une autre méthode définie dans la même classe de base peut en définitive appeler une méthode d'une classe dérivée qui a redéfini cette méthode. (Pour les programmeurs C++: toutes les méthodes en Python sont des ``fonctions virtuelles''.)

Une méthode d'une classe dérivée qui redéfinit une fonction peut en fait vouloir étendre et non pas remplacer la méthode de la classe de base de même nom. Il y un moyen simple d'appeler la méthode de la classe de base directement: simplement appelez "NomClasseDeBase.nommethode(self, arguments)". Ceci peut parfois s'avérer utile pour les clients aussi. (Remarquez que ça ne marche que si la classe de base est définie ou importée dans la portée globale.)


9.5.1 Héritage Multiple

Python supporte aussi une forme limitée d'héritage multiple. Une définition de classe avec plusieurs classes de base ressemble à:

class NomClasseDerivee(Base1, Base2, Base3):
    <instruction-1>
    .
    .
    .
    <instruction-N>

La seule règle permettant d'expliquer la sémantique de l'héritage multiple est la règle de résolution utilisée pour les références aux attributs. La résolution se fait en profondeur d'abord, de gauche à droite. Donc, si un attribut n'est pas trouvé dans NomClasseDerivee, il est cherché dans Base1, puis (récursivement) dans les classes de base de Base1, et seulement s'il n'y est pas trouvé, il est recherché dans Base2, et ainsi de suite.

(Pour certains la recherche en largeur d'abord -- chercher dans Base2 est Base3 avant les classes de base de Base1 -- semble plus naturelle. Pourtant, cela nécessite que vous sachiez si un attribut particulier de Base1 est défini dans Base1 ou dans une de ses classes de base avant de pouvoir considérer les conflits de nom avec Base2. La règle en profondeur d'abord ne fait pas de différence entre les attributs directs et hérités de Base1.)

Il est clair que l'utilisation banalisée de l'héritage multiple est un cauchemar de maintenance, étant donné que Python se base sur des conventions pour éviter les conflits de noms accidentels. Un problème bien connu de l'héritage multiple est celui d'une classe dérivée de deux autres classes qui ont une même classe de base en commun. S'il reste facile de voir ce qui se passe dans ce cas (l'instance aura une seule copie des ``variables d'instance'' ou des données attributs utilisés par la classe de base commune), il n'est pas clair que cette sémantique soit utile de quelque façon que ce soit.


9.6 Variables Privées

Il y a un support limité pour des identificateurs privés dans une classe. Tout identificateur de la forme __spam (au moins deux tirets-bas au début, au plus un tiret-bas à la fin) est maintenant textuellement remplacé par _nomclasse__spam, où nomclasse est le nom de classe courant, duquel les tirets-bas de début on été enlevés. Ce brouillage (mangling) est réalisé indépendamment de la position syntaxique de l'identificateur, donc il peut être utilisé pour définir des variables de classe et d'instance privées, des méthodes, des globales, et même pour enregistrer des variables d'instance privées de cette classe dans des instances d'autres classes. Le nom brouillé peut être tronqué s'il dépasse 255 caractères. En dehors des classes, ou lorsque le nom de la classe ne contient que des tirets-bas, le brouillage n'a pas lieu.

Le brouillage de noms permet aux classes de définir simplement des variables d'instance et des méthodes ``privées'', sans avoir à se préoccuper des variables d'instance définies par des classes dérivées, ou des problèmes avec des variables d'instance définies en dehors de la classe. Remarquez que les règles de brouillage ont été dessinées surtout pour éviter des accidents; il reste possible d'accéder ou de modifier une variable considérée comme privée. Ceci peut être utile, par exemple pour le déboguage, et c'est une des raisons pour lesquelles ce trou n'est pas comblé. (Petite bogue: dériver une classe en utilisant le même nom de classe permet l'utilisation des variables privées de la classe de base.)

Remarquez que le code passé en argument à exec, eval() ou evalfile() ne considère pas le nom de classe de la classe appelante comme étant le nom de classe courant; c'est un effet similaire à celui de l'instruction global, limité à du code qui a été compilé en même temps. La même restriction s'applique à getattr(), setattr() et delattr(), de même qu'aux références directes à __dict__.

Voici l'exemple d'une classe qui implémente ses propres méthodes __getattr__ et __setattr__ et enregistre tous les attributs dans une variable privée.

class AttributsVirtuels:
    __vdict = None
    __vdict_name = locals().keys()[0]
     
    def __init__(self):
        self.__dict__[self.__vdict_name] = {}
    
    def __getattr__(self, name):
        return self.__vdict[name]
    
    def __setattr__(self, name, value):
        self.__vdict[name] = value


9.7 En Vrac

Il est parfois utile de disposer d'un type de données semblable au ``record'' du Pascal ou au ``struct'' du C, pour lier quelques données nommées. Une définition de classe vide peut servir à cela:

class Employe:
    pass

john = Employe() # Crée un enregistrement vide d'Employe

# Remplit les champs de l'enregistrement
john.nom = 'John Doe'
john.dept = 'computer lab'
john.salaire = 1000

Un bout de code Python qui attend des données d'un certain type abstrait peut souvent recevoir à la place une classe qui simule les méthodes de ce type de données. Par exemple, si vous avez une fonction qui met en forme des données issues d'un objet fichier, vous pouvez définir une classe avec des méthodes read() et readline() qui prend les données d'un tampon et passer celui-ci comme argument.

Les objets méthode d'instance possèdent eux-mêmes des attributs: m.im_self est l'objet duquel la méthode est instance, et m.im_func est l'objet fonction correspondant à la méthode.


9.8 Les Exceptions Peuvent Etre des Classes

Les exceptions définies par l'utilisateur ne sont pas limitées à être des chaînes de caractères -- elles peuvent être aussi identifiés comme des classes. En utilisant ce mécanisme, on peut définir des hiérarchies extensibles d'exceptions.

Il y a deux formes sémantiques valides pour l'instruction raise:

raise Classe, instance

raise instance

Dans la première forme, instance doit être une instance de Classe ou d'une de ses classes dérivées. La seconde forme est un raccourci pour

raise instance.__class__, instance

Une clause d'exception peut lister des classes et des chaînes de caractères. Une classe dans une clause d'exception est compatible avec une exception si celle ci est la même classe ou une classe qui en est dérivée (mais pas en sens inverse -- une clause d'exception qui liste une classe dérivée n'est pas compatible avec une classe de base). Par exemple, le code suivant affichera B, C, D, dans cet ordre:

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"

Remarquez que si les clauses d'exception avaient été inversées ("except B" en premier), le code aurait affiché B, B, B -- c'est la première clause except qui convient qui est exécutée.

Lorsqu'un message d'erreur est affiché pour une exception non gérée qui est une classe, le nom de la classe est affiché, puis deux-points, un espace, et finalement, l'instance convertie en chaîne de caractères à travers la fonction intégrée str().



Notes

...9.1
Sauf pour une chose. Les objets module possèdent un attribut secret en lecture exclusive qui s'appelle __dict__ et qui renvoie le dictionnaire utilisé pour implémenter l'espace de noms du module; le nom __dict__ est un attribut mais pas un nom global. Evidemment, l'utiliser casse l'abstraction de l' implémentation des espaces de noms, et son usage doit être restreint à des choses telles que les débogueurs post-mortem.

See About this document... for information on suggesting changes.