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