9. Classes
**********

La notion de classes en Python s’inscrit dans le langage avec un
minimum de syntaxe et de sémantique nouvelles. C’est un mélange des
mécanismes rencontrés dans C++ et Modula-3. Les classes fournissent
toutes les fonctionnalités standards de la programmation orientée
objet : l’héritage de classes autorise les héritages multiples, une
classe dérivée peut surcharger les méthodes de sa ou ses classes de
base et une méthode peut appeler la méthode d’une classe de base qui
possède le même nom. Les objets peuvent contenir n’importe quel nombre
ou type de données. De la même manière que les modules, les classes
participent à la nature dynamique de Python : elles sont créées
pendant l’exécution et peuvent être modifiées après leur création.

Dans la terminologie C++, les membres des classes (y compris les
données) sont *publics* (sauf exception, voir Variables privées et
références locales aux classes) et toutes les fonctions membres sont
*virtuelles*. Comme avec Modula-3, il n’y a aucune façon d’accéder aux
membres d’un objet à partir de ses méthodes : une méthode est déclarée
avec un premier argument explicite représentant l’objet et cet
argument est transmis de manière implicite lors de l’appel. Comme avec
Smalltalk, les classes elles-mêmes sont des objets. Il existe ainsi
une sémantique pour les importer et les renommer. Au contraire de C++
et Modula-3, les types natifs peuvent être utilisés comme classes de
base pour être étendus par l’utilisateur. Enfin, comme en C++, la
plupart des opérateurs natifs avec une syntaxe spéciale (opérateurs
arithmétiques, indiçage, etc.) peuvent être redéfinis pour les
instances de classes.

Par manque d’ontologie pour parler des classes, nous utilisons parfois
des termes de Smalltalk et C++. Nous voulions utiliser les termes de
Modula-3 puisque sa sémantique orientée objet est plus proche de celle
de Python que C++, mais il est probable que seul un petit nombre de
lecteurs les connaissent.


9.1. Objets et noms : préambule
===============================

Les objets possèdent une existence propre et plusieurs noms peuvent
être utilisés (dans divers contextes) pour faire référence à un même
objet. Ce concept est connu sous le nom d’alias dans d’autres
langages. Il  n’apparaît pas au premier coup d’œil en Python et il
peut être ignoré tant qu’on travaille avec des types de base immuables
(nombres, chaînes, tuples). Cependant, les alias peuvent produire des
effets surprenants sur la sémantique d’un code Python mettant en jeu
des objets muables comme les listes, les dictionnaires et la plupart
des autres types. En général, leur utilisation est bénéfique au
programme car les alias se comportent, d’un certain point de vue,
comme des pointeurs. Par exemple, transmettre un objet n’a aucun coût
car c’est simplement un pointeur qui est transmis par l’implémentation
; et si une fonction modifie un objet passé en argument, le code à
l’origine de l’appel voit le changement. Ceci élimine le besoin
d’avoir deux mécanismes de transmission d’arguments comme en Pascal.


9.2. Portées et espaces de noms en Python
=========================================

Avant de présenter les classes, nous devons aborder la notion de
portée en Python. Les définitions de classes font d’habiles
manipulations avec les espaces de noms, vous devez donc savoir comment
les portées et les espaces de noms fonctionnent. Soit dit en passant,
la connaissance de ce sujet est aussi utile aux développeurs Python
expérimentés.

Commençons par quelques définitions.

Un *espace de noms* est une table de correspondance entre des noms et
des objets. La plupart des espaces de noms sont actuellement
implémentés sous forme de dictionnaires Python, mais ceci n’est
normalement pas visible (sauf pour les performances) et peut changer
dans le futur. Comme exemples d’espaces de noms, nous pouvons citer
les primitives (fonctions comme "abs()" et les noms des exceptions de
base) ; les noms globaux dans un module ; et les noms locaux lors d’un
appel de fonction. D’une certaine manière, l’ensemble des attributs
d’un objet forme lui-même un espace de noms. L’important à retenir
concernant les espaces de noms est qu’il n’y a absolument aucun lien
entre les noms de différents espaces de noms ; par exemple, deux
modules différents peuvent définir une fonction "maximize" sans qu’il
n’y ait de confusion. Les utilisateurs des modules doivent préfixer le
nom de la fonction avec celui du module.

À ce propos, nous utilisons le mot *attribut* pour tout nom suivant un
point. Par exemple, dans l’expression "z.real", "real" est un attribut
de l’objet "z". Rigoureusement parlant, les références à des noms dans
des modules sont des références d’attributs : dans l’expression
"nommodule.nomfonction", "nommodule" est un objet module et
"nomfonction" est un attribut de cet objet. Dans ces conditions, il
existe une correspondance directe entre les attributs du module et les
noms globaux définis dans le module : ils partagent le même espace de
noms [1] !

Les attributs peuvent être en lecture seule ou modifiables. S’ils sont
modifiables, l’affectation à un attribut est possible. Les attributs
de modules sont modifiables : vous pouvez écrire "nommodule.la_reponse
= 42". Les attributs modifiables peuvent aussi être effacés avec
l’instruction "del". Par exemple, "del nommodule.la_reponse" supprime
l’attribut "la_reponse" de l’objet nommé "nommodule".

Les espaces de noms sont créés à différents moments et ont différentes
durées de vie. L’espace de noms contenant les primitives est créé au
démarrage de l’interpréteur Python et n’est jamais effacé. L’espace de
nom global pour un module est créé lorsque la définition du module est
lue. Habituellement, les espaces de noms des modules durent aussi
jusqu’à l’arrêt de l’interpréteur. Les instructions exécutées par la
première invocation de l’interpréteur, qu’ils soient lus depuis un
fichier de script ou de manière interactive, sont considérés comme
faisant partie d’un module appelé "__main__", de façon qu’elles
possèdent leur propre espace de noms. (les primitives vivent elles-
mêmes dans un module, appelé "builtins".)

L’espace des noms locaux d’une fonction est créé lors de son appel,
puis effacé lorsqu’elle renvoie un résultat ou lève une exception non
prise en charge (en fait, « oublié » serait une meilleure façon de
décrire ce qui se passe réellement). Bien sûr, des invocations
récursives ont chacune leur propre espace de noms.

La *portée* est la zone textuelle d’un programme Python où un espace
de noms est directement accessible. « Directement accessible »
signifie ici qu’une référence non qualifiée à un nom est cherchée dans
l’espace de noms.

Bien que les portées soient déterminées de manière statique, elles
sont utilisées de manière dynamique. À n’importe quel moment de
l’exécution, il y a au minimum trois portées imbriquées dont les
espaces de noms sont directement accessibles :

* la portée la plus au centre, celle qui est consultée en premier,
  contient les noms locaux ;

* les portées des fonctions englobantes, qui sont consultées en
  commençant avec la portée englobante la plus proche, contiennent des
  noms non-locaux mais aussi non-globaux ;

* l’avant-dernière portée contient les noms globaux du module
  courant ;

* la portée englobante, consultée en dernier, est l’espace de noms
  contenant les primitives.

Si un nom est déclaré global, toutes les références et affectations
vont directement dans la portée intermédiaire contenant les noms
globaux du module. Dans les autres scas, toutes les variables trouvées
au dehors du scope le plus proche seront en lecture seule (toute
tentative de modifier une telle variable créera simplement une
*nouvelle* variable locale dans la portée la plus au centre, en
laissant inchangée la variable du même nom dans la portée englobante).

Habituellement, la portée locale référence les noms locaux de la
fonction courante. En dehors des fonctions, la portée locale référence
le même espace de noms que la portée globale : l’espace de noms du
module. Les définitions de classes créent un nouvel espace de noms
dans la portée locale.

Il est important de réaliser que les portées sont déterminées de
manière textuelle : la portée globale d’une fonction définie dans un
module est l’espace de noms de ce module, quelle que soit la
provenance de l’appel à la fonction. En revanche, la recherche réelle
des noms est faite dynamiquement au moment de l’exécution. Cependant
la définition du langage est en train d’évoluer vers une résolution
statique des noms au moment de la « compilation », donc ne vous basez
pas sur une résolution dynamique (en réalité, les variables locales
sont déjà déterminées de manière statique) !

Une particularité de Python est que si aucune instruction "global"
n’est active, les affectations de noms vont toujours dans la portée la
plus proche. Les affectations ne copient aucune donnée : elles se
contentent de lier des noms à des objets. Ceci est également vrai pour
l’effacement : l’instruction "del x" supprime la liaison de "x" dans
l’espace de noms référencé par la portée locale. En réalité, toutes
les opérations qui impliquent des nouveaux noms utilisent la portée
locale : en particulier, les instructions "import" et les définitions
de fonctions effectuent une liaison du module ou du nom de fonction
dans la portée locale. (L’instruction "global" peut être utilisée pour
indiquer qu’une variable particulière doit être dans l’espace de noms
global.)


9.3. Une première approche des classes
======================================

Le concept de classe introduit un peu de syntaxe nouvelle, trois
nouveaux types d’objets ainsi que quelques nouveaux éléments de
sémantique.


9.3.1. Syntaxe de définition des classes
----------------------------------------

La forme la plus simple de définition d’une classe est la suivante :

   class ClassName:
       <statement-1>
       .
       .
       .
       <statement-N>

Les définitions de classes, comme les définitions de fonctions
(définitions "def"), doivent être exécutées avant d’avoir un effet.
Vous pouvez tout à fait placer une définition de classe dans une
branche d’une instruction conditionnelle "if" ou encore à l’intérieur
d’une fonction.

Dans la pratique, les déclarations dans une définition de classe sont
généralement des définitions de fonctions mais d’autres déclarations
sont permises et parfois utiles (nous revenons sur ce point plus
tard). Les définitions de fonction à l’intérieur d’une classe ont
normalement une forme particulière de liste d’arguments, dictée par
les conventions d’appel aux méthodes (à nouveau, tout ceci est
expliqué plus loin).

Quand une classe est définie, un nouvel espace de noms est créé et
utilisé comme portée locale — Ainsi, toutes les affectations de
variables locales entrent dans ce nouvel espace de noms. En
particulier, les définitions de fonctions y lient le nom de la
nouvelle fonction.

À la fin de la définition d’une classe, un *objet classe* est créé.
C’est, pour simplifier, une encapsulation du contenu de l’espace de
noms créé par la définition de classe. Nous revoyons les objets
classes dans la prochaine section. La portée locale initiale (celle
qui prévaut avant le début de la définition de la classe) est
ré-instanciée et l’objet de classe est lié ici au nom de classe donné
dans l’en-tête de définition de classe ("ClassName" dans l’exemple).


9.3.2. Objets classes
---------------------

Les objets classes prennent en charge deux types d’opérations : des
références à des attributs et l’instanciation.

Les *références d’attributs* utilisent la syntaxe standard utilisée
pour toutes les références d’attributs en Python : "obj.nom". Les noms
d’attribut valides sont tous les noms qui se trouvaient dans l’espace
de noms de la classe quand l’objet classe a été créé. Donc, si la
définition de classe est de cette forme :

   class MyClass:
       """A simple example class"""
       i = 12345

       def f(self):
           return 'hello world'

alors "MyClass.i" et "MyClass.f" sont des références valides à des
attributs, renvoyant respectivement un entier et un objet fonction.
Les attributs de classes peuvent également être affectés, de sorte que
vous pouvez modifier la valeur de "MyClass.i" par affectation.
"__doc__" est aussi un attribut valide, renvoyant la *docstring*
appartenant à la classe : ""A simple example class"".

L”*instanciation* de classes utilise la notation des fonctions.
Considérez simplement que l’objet classe est une fonction sans
paramètre qui renvoie une nouvelle instance de la classe. Par exemple
(en considérant la classe définie ci-dessus) :

   x = MyClass()

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

L’opération d’instanciation (en « appelant » un objet classe) crée un
objet vide. De nombreuses classes aiment créer des instances
personnalisées correspondant à un état initial spécifique. À cet
effet, une classe peut définir une méthode spéciale nommée
"__init__()", comme ceci :

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

Quand une classe définit une méthode "__init__()", l’instanciation de
la classe appelle automatiquement "__init__()" pour la nouvelle
instance de la classe. Donc, dans cet exemple, l’initialisation d’une
nouvelle instance peut être obtenue par :

   x = MyClass()

Bien sûr, la méthode "__init__()" peut avoir des arguments pour une
plus grande flexibilité. Dans ce cas, les arguments donnés à
l’opérateur d’instanciation de classe sont transmis à "__init__()".
Par exemple :

   >>> class Complex:
   ...     def __init__(self, realpart, imagpart):
   ...         self.r = realpart
   ...         self.i = imagpart
   ...
   >>> x = Complex(3.0, -4.5)
   >>> x.r, x.i
   (3.0, -4.5)


9.3.3. Objets instances
-----------------------

Maintenant, que pouvons-nous faire avec des objets instances ? Les
seules opérations comprises par les objets instances sont des
références d’attributs. Il y a deux sortes de noms d’attributs
valides, les attributs “données” et les méthodes.

Les *attributs “données”* correspondent à des « variables d’instance »
en Smalltalk et aux « membres de données » en C++. Les attributs
“données” n’ont pas à être déclarés. Comme les variables locales, ils
existent dès lors qu’ils sont assignés une première fois. Par exemple,
si "x" est l’instance de "MyClass" créée ci-dessus, le code suivant
affiche la valeur "16", sans laisser de trace :

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

L’autre type de référence à un attribut d’instance est une *méthode*.
Une méthode est une fonction qui « appartient à » un objet (en Python,
le terme de méthode n’est pas unique aux instances de classes :
d’autres types d’objets peuvent aussi avoir des méthodes. Par exemple,
les objets listes ont des méthodes appelées "append", "insert",
"remove", "sort" et ainsi de suite. Toutefois, dans la discussion qui
suit, sauf indication contraire, nous utilisons le terme de méthode
exclusivement en référence à des méthodes d’objets instances de
classe).

Les noms de méthodes valides d’un objet instance dépendent de sa
classe. Par définition, tous les attributs d’une classe qui sont des
objets  fonction définissent les méthodes correspondantes de ses
instances. Donc, dans notre exemple, "x.f" est une référence valide à
une méthode car "MyClass.f" est une fonction, mais pas "x.i" car
"MyClass.i" n’en est pas une. Attention cependant, "x.f" n’est pas la
même chose que "MyClass.f" — Il s’agit d’un *objet méthode*, pas d’un
objet fonction.


9.3.4. Objets méthode
---------------------

Le plus souvent, une méthode est appelée juste après avoir été liée :

   x.f()

Dans l’exemple de la classe "MyClass", cela renvoie la chaîne de
caractères "hello world". Toutefois, il n’est pas nécessaire d’appeler
la méthode directement: "x.f" est un objet méthode, il peut être gardé
de coté et être appelé plus tard. Par exemple :

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

affiche "hello world" jusqu’à la fin des temps.

Que se passe-t-il exactement quand une méthode est appelée ? Vous avez
dû remarquer que "x.f()" a été appelée dans le code ci-dessus sans
argument, alors que la définition de la méthode "f()" spécifie bien
qu’elle prend un argument. Qu’est-il arrivé à l’argument ? Python doit
sûrement lever une exception lorsqu’une fonction qui requiert un
argument est appelée sans – même si l’argument n’est pas utilisé…

En fait, vous aurez peut-être deviné la réponse : la particularité des
méthodes est que l’objet est passé comme premier argument de la
fonction. Dans notre exemple, l’appel "x.f ()" est exactement
équivalent à "MaClasse.f(x)". En général, appeler une méthode avec une
liste d’arguments *n* est équivalent à appeler la fonction
correspondante avec cette liste d’arguments modulo l’insertion de
l’objet de la méthode avant le premier argument.

If you still don’t understand how methods work, a look at the
implementation can perhaps clarify matters.  When a non-data attribute
of an instance is referenced, the instance’s class is searched.  If
the name denotes a valid class attribute that is a function object, a
method object is created by packing (pointers to) the instance object
and the function object just found together in an abstract object:
this is the method object.  When the method object is called with an
argument list, a new argument list is constructed from the instance
object and the argument list, and the function object is called with
this new argument list.


9.3.5. Classes et variables d’instance
--------------------------------------

En général, les variables d’instance stockent des informations
relatives à chaque instance alors que les variables de classe servent
à stocker les attributs et méthodes communes à toutes les instances de
la classe :

   class Dog:

       kind = 'canine'         # class variable shared by all instances

       def __init__(self, name):
           self.name = name    # instance variable unique to each instance

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.kind                  # shared by all dogs
   'canine'
   >>> e.kind                  # shared by all dogs
   'canine'
   >>> d.name                  # unique to d
   'Fido'
   >>> e.name                  # unique to e
   'Buddy'

Comme nous l’avons vu dans Objets et noms : préambule, les données
partagées *muable* (telles que les listes, dictionnaires, etc…)
peuvent avoir des effets surprenants. Par exemple, la liste *tricks*
dans le code suivant ne devrait pas être utilisée en tant que variable
de classe car, dans ce cas, une seule liste est partagée par toutes
les instances de *Dog* :

   class Dog:

       tricks = []             # mistaken use of a class variable

       def __init__(self, name):
           self.name = name

       def add_trick(self, trick):
           self.tricks.append(trick)

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.add_trick('roll over')
   >>> e.add_trick('play dead')
   >>> d.tricks                # unexpectedly shared by all dogs
   ['roll over', 'play dead']

Une conception correcte de la classe est d’utiliser une variable
d’instance à la place :

   class Dog:

       def __init__(self, name):
           self.name = name
           self.tricks = []    # creates a new empty list for each dog

       def add_trick(self, trick):
           self.tricks.append(trick)

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.add_trick('roll over')
   >>> e.add_trick('play dead')
   >>> d.tricks
   ['roll over']
   >>> e.tricks
   ['play dead']


9.4. Remarques diverses
=======================

Les attributs “données” surchargent les méthodes avec le même nom ;
pour éviter des conflits de nommage, qui peuvent causer des bugs
difficiles à trouver dans de grands programmes, il est sage d’adopter
certaines conventions qui minimisent les risques de conflits. Parmi
les conventions possibles, on peut citer la mise en majuscule des noms
de méthodes, le préfixe des noms d’attributs “données” par une chaîne
courte et unique (parfois juste la caractère souligné) ou
l’utilisation de verbes pour les méthodes et de noms pour les
attributs “données”.

Les attributs “données” peuvent être référencés par des méthodes comme
par des utilisateurs ordinaires (« clients ») d’un objet. En d’autres
termes, les classes ne sont pas utilisables pour implémenter des types
de données purement abstraits. En fait, il n’est pas possible en
Python d’imposer de masquer des données — tout est basé sur des
conventions (d’un autre coté, l’implémentation de Python, écrite en C,
peut complètement masquer les détails d’implémentation et contrôler
l’accès à un objet si nécessaire ; ceci peut être utilisé par des
extensions de Python écrites en C).

Les clients doivent utiliser les attributs “données” avec précaution —
ils pourraient mettre le désordre dans les invariants gérés par les
méthodes avec leurs propres valeurs d’attributs. Remarquez que les
clients peuvent ajouter leurs propres attributs “données” à une
instance d’objet sans altérer la validité des méthodes, pour autant
que les noms n’entrent pas en conflit — là aussi, adopter une
convention de nommage peut éviter bien des problèmes.

Il n’y a pas de notation abrégée pour référencer des attributs
“données” (ou les autres méthodes !) depuis les méthodes. Nous pensons
que ceci améliore en fait la lisibilité des méthodes : il n’y a aucune
chance de confondre variables locales et variables d’instances quand
on regarde le code d’une méthode.

Souvent, le premier argument d’une méthode est nommé "self". Ce n’est
qu’une convention : le nom "self" n’a aucune signification
particulière en Python. Notez cependant que si vous ne suivez pas
cette convention, votre code risque d’être moins lisible pour d’autres
programmeurs Python et il est aussi possible qu’un programme qui fasse
l’introspection de classes repose sur une telle convention.

Tout objet fonction qui est un attribut de classe définit une méthode
pour des instances de cette classe. Il n’est pas nécessaire que le
texte de définition de la fonction soit dans la définition de la
classe : il est possible d’affecter un objet fonction à une variable
locale de la classe. Par exemple :

   # Function defined outside the class
   def f1(self, x, y):
       return min(x, x+y)

   class C:
       f = f1

       def g(self):
           return 'hello world'

       h = g

Maintenant, "f", "g" et "h" sont toutes des attributs de la classe "C"
et font référence à des fonctions objets. Par conséquent, ce sont
toutes des méthodes des instances de "C" — "h" est exactement
identique à "g". Remarquez qu’en pratique, ceci ne sert qu’à
embrouiller le lecteur d’un programme.

Les méthodes peuvent appeler d’autres méthodes en utilisant des
méthodes qui sont des attributs de l’argument "self" :

   class Bag:
       def __init__(self):
           self.data = []

       def add(self, x):
           self.data.append(x)

       def addtwice(self, x):
           self.add(x)
           self.add(x)

Les méthodes peuvent faire référence à des noms globaux de la même
manière que les fonctions. La portée globale associée à une méthode
est le module contenant la définition de la classe (la classe elle-
même n’est jamais utilisée en tant que portée globale). Alors qu’il
est rare d’avoir une bonne raison d’utiliser des données globales dans
une méthode, il y a de nombreuses utilisations légitimes de la portée
globale : par exemple, les fonctions et modules importés dans une
portée globale peuvent être utilisés par des méthodes, de même que les
fonctions et classes définies dans cette même portée. Habituellement,
la classe contenant la méthode est elle-même définie dans cette portée
globale et, dans la section suivante, nous verrons de bonnes raisons
pour qu’une méthode référence sa propre classe.

Toute valeur est un objet et a donc une *classe* (appelée aussi son
*type*). Elle est stockée dans "objet.__class__".


9.5. Héritage
=============

Bien sûr, ce terme de « classe » ne serait pas utilisé s’il n’y avait
pas d’héritage. La syntaxe pour définir une sous-classe est de cette
forme :

   class DerivedClassName(BaseClassName):
       <statement-1>
       .
       .
       .
       <statement-N>

Le nom "BaseClassName" doit être défini dans une portée contenant la
définition de la classe dérivée. À la place du nom d’une classe de
base, une expression est aussi autorisée. Ceci peut être utile, par
exemple, lorsque la classe est définie dans un autre module :

   class DerivedClassName(modname.BaseClassName):

L’exécution d’une définition de classe dérivée se déroule comme pour
une classe de base. Quand l’objet de la classe est construit, la
classe de base est mémorisée. Elle est utilisée pour la résolution des
références d’attributs : si un attribut n’est pas trouvé dans la
classe, la recherche se poursuit en regardant dans la classe de base.
Cette règle est appliquée récursivement si la classe de base est elle-
même dérivée d’une autre classe.

Il n’y a rien de particulier dans l’instanciation des classes dérivées
: "DerivedClassName()" crée une nouvelle instance de la classe. Les
références aux méthodes sont résolues comme suit : l’attribut
correspondant de la classe est recherché, en remontant la hiérarchie
des classes de base si nécessaire, et la référence de méthode est
valide si cela conduit à une fonction.

Les classes dérivées peuvent surcharger des méthodes de leurs classes
de base. Comme les méthodes n’ont aucun privilège particulier quand
elles appellent d’autres méthodes d’un même objet, une méthode d’une
classe de base qui appelle une autre méthode définie dans la même
classe peut en fait appeler une méthode d’une classe dérivée qui la
surcharge (pour les programmeurs C++ : toutes les méthodes de Python
sont en effet « virtuelles »).

Une méthode dans une classe dérivée peut aussi, en fait, vouloir
étendre plutôt que simplement remplacer la méthode du même nom de sa
classe de base. L’appel direct à la méthode de la classe de base
s’écrit simplement "BaseClassName.nomMethode(self, arguments)". C’est
parfois utile également aux clients (notez bien que ceci ne fonctionne
que si la classe de base est accessible en tant que "BaseClassName"
dans la portée globale).

Python définit deux fonctions primitives pour gérer l’héritage :

* utilisez "isinstance()" pour tester le type d’une instance :
  "isinstance(obj, int)" renvoie "True" seulement si "obj.__class__"
  est égal à "int" ou à une autre classe dérivée de "int" ;

* Utilisez "issubclass()" pour tester l’héritage d’une class :
  "issubclass(bool, int)" renvoie "True" car la class "bool" est une
  sous-classe de "int". Par contre, "issubclass(unicode, str)" renvoie
  "False" car "unicode" n’est pas une sous-classe de "str" (ils
  partagent seulement un ancêtre commun, "basestring").


9.5.1. Héritage multiple
------------------------

Python propose également une forme d’héritage multiple. Une définition
de classe ayant plusieurs classes de base ressemble à :

   class DerivedClassName(Base1, Base2, Base3):
       <statement-1>
       .
       .
       .
       <statement-N>

Pour les anciennes classes, la seule règle est : en profondeur, de
gauche à droite. Ainsi, si un attribut n’est pas trouvé dans
"NomDeLaClasseDerivee", il est recherché dans "Base1", puis
(récursivement) dans les classes de base de "Base1" ; s’il n’y est pas
trouvé, il est recherché dans "Base2" et ses classes de base, et ainsi
de suite.

(Pour certaines personnes, commencer la recherche « en largeur » —
chercher dans "Base2" et "Base3" avant d’aller dans les classes de
base de "Base1" — peut sembler plus naturel. Toutefois, ceci
nécessiterait de savoir si un attribut particulier de "Base1" est
actuellement défini dans "Base1" ou dans l’une de ses classes de base
avant de pouvoir envisager les conséquences d’un conflit de nom avec
un attribut de "Base2". La règle qui consiste à rechercher d’abord en
profondeur ne fait aucune différence entre des attributs définis
localement et des attributs hérités de "Base1").

Pour les *nouvelles classes*, l’ordre de résolution des méthodes
change dynamiquement pour gérer les appels coopératifs de la fonction
"super()". Cette approche est connue dans certains langages supportant
l’héritage multiple sous le nom de la « méthode la plus proche » («
call-next-method »), et est plus puissante que le seul appel de la
méthode « super » que l’on trouve dans les langages ne gérant que
l’héritage simple.

Avec les nouvelles classes, l’ordre défini dynamiquement est
nécessaire car tous les cas d’héritage multiple comportent un arbre
d’héritage en losange (au moins l’une des classes parentes peut être
accédée via plusieurs chemins à partir d’une même sous-classe). Par
exemple, toutes les nouvelles classes héritent de "object", donc
n’importe quel arbre d’héritage multiple fournit plus d’un chemin pour
atteindre "object". Pour qu’une classe de base ne soit pas appelée
plusieurs fois, l’algorithme dynamique linéarise l’ordre de recherche
d’une façon qui préserve l’ordre d’héritage, de la gauche vers la
droite, spécifié dans chaque classe, qui appelle chaque classe parente
une seule fois, qui est monotone (ce qui signifie qu’une classe peut
être sous-classée sans affecter l’ordre d’héritage de ses parents).
Prises ensemble, ces propriétés permettent de concevoir des classes de
façon fiable et extensible dans un contexte d’héritage multiple. Pour
plus de détail, consultez
http://www.python.org/download/releases/2.3/mro/.


9.6. Variables privées et références locales aux classes
========================================================

Les membres « privés », qui ne peuvent être accédés que depuis
l’intérieur d’un objet, n’existent pas en Python. Toutefois, il existe
une convention respectée par la majorité du code Python : un nom
préfixé par un tiret bas (comme "_spam") doit être considéré comme une
partie non publique de l’API (qu’il s’agisse d’une fonction, d’une
méthode ou d’un attribut “données”). Il doit être vu comme un détail
d’implémentation pouvant faire l’objet de modifications futures sans
préavis.

Dès lors qu’il y a un cas d’utilisation valable pour avoir des
attributs privés aux classes (notamment pour éviter des conflits avec
des noms définis dans des sous-classes), il existe un support (certes
limité) pour un tel mécanisme, appelé *name mangling*. Tout
identifiant de la forme "__spam" (avec au moins deux tirets bas en
tête et au plus un à la fin) est remplacé textuellement par
"_classname__spam", où "classname" est le nom de la classe sans le ou
les premiers tirets-bas. Ce « découpage » est effectué sans tenir
compte de la position syntaxique de l’identifiant, tant qu’il est
présent dans la définition d’une classe.

Ce changement de nom est utile pour permettre à des sous-classes de
surcharger des méthodes sans casser les appels de méthodes à
l’intérieur d’une classe. Par exemple :

   class Mapping:
       def __init__(self, iterable):
           self.items_list = []
           self.__update(iterable)

       def update(self, iterable):
           for item in iterable:
               self.items_list.append(item)

       __update = update   # private copy of original update() method

   class MappingSubclass(Mapping):

       def update(self, keys, values):
           # provides new signature for update()
           # but does not break __init__()
           for item in zip(keys, values):
               self.items_list.append(item)

The above example would work even if "MappingSubclass" were to
introduce a "__update" identifier since it is replaced with
"_Mapping__update" in the "Mapping" class  and
"_MappingSubclass__update" in the "MappingSubclass" class
respectively.

Notez que ces règles sont conçues avant tout pour éviter les accidents
; il reste possible d’accéder ou de modifier une variable considérée
comme privée. Ceci peut même être utile dans certaines circonstances,
comme au sein du débogueur.

Notez que le code passé à "exec()", "eval()" ou "execfile()" ne
considère pas le nom de la classe appelante comme étant la classe
courante ; le même effet s’applique à la directive "gloabl", dont
l’effet est de la même façon restreint au code compilé dans le même
ensemble de byte-code. Les mêmes restrictions s’appliquent à
"getattr()", "setattr()" et "delattr()", ainsi qu’aux références
directes à "__dict__".


9.7. Trucs et astuces
=====================

Il est parfois utile d’avoir un type de donnée similaire au « record »
du Pascal ou au *struct* du C, qui regroupent ensemble quelques
attributs “données” nommés. La définition d’une classe vide remplit
parfaitement ce besoin :

   class Employee:
       pass

   john = Employee()  # Create an empty employee record

   # Fill the fields of the record
   john.name = 'John Doe'
   john.dept = 'computer lab'
   john.salary = 1000

À du code Python qui s’attend à recevoir un type de donnée abstrait
spécifique, on peut souvent fournir une classe qui simule les méthodes
de ce type. Par exemple, à une fonction qui formate des données
extraites d’un objet fichier, vous pouvez lui passer comme argument
une instance d’une classe qui implémente les méthodes "read()" et
"readline()" en puisant ses données à partir d’un tampon de chaînes de
caractères.

Les objets méthodes d’instances ont également des attributs :
"m.im_self" est l’instance d’objet avec la méthode "m()", et
"m.im_func" est l’objet fonction correspondant à la méthode.


9.8. Les exceptions sont aussi des classes
==========================================

Les exceptions définies par l’utilisateur sont également définies par
des classes. En utilisant ce mécanisme, il est possible de créer des
hiérarchies d’exceptions extensibles.

Il y a deux nouvelles formes (sémantiques) pour l’instruction "raise"
:

   raise Class, instance

   raise instance

Dans la première forme, "instance" doit être une instance de "Class"
ou d’une classe dérivée. La seconde forme est un raccourci pour :

   raise instance.__class__, instance

Une classe dans une clause "except" est compatible avec une exception
si elle est de la même classe ou d’une de ses classes dérivées. Mais
l’inverse n’est pas vrai, une clause "except" spécifiant une classe
dérivée n’est pas compatible avec une classe de base. Par exemple, le
code suivant affiche B, C et 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"

Notez que si les clauses "except" avaient été inversées (avec "except
B" en premier), il aurait affiché B, B, B — la première clause
"except" correspondante étant déclenchée.

Quand un message d’erreur est imprimé pour une exception non traitée,
la classe de l’exception est indiquée, suivie de deux points, d’un
espace et de l’instance convertie en chaîne de caractères via la
fonction "str()".


9.9. Itérateurs
===============

Vous avez maintenant certainement remarqué que l’on peut itérer sur la
plupart des objets conteneurs en utilisant une instruction "for" :

   for element in [1, 2, 3]:
       print element
   for element in (1, 2, 3):
       print element
   for key in {'one':1, 'two':2}:
       print key
   for char in "123":
       print char
   for line in open("myfile.txt"):
       print line,

Ce mode d’accès est simple, concis et pratique. L’utilisation
d’itérateurs imprègne et unifie Python. En arrière plan, l’instruction
"for" appelle la fonction "iter()" sur l’objet conteneur. Cette
fonction renvoie un itérateur qui définit la méthode "next()",
laquelle accède aux éléments du conteneur un par un. Lorsqu’il n’y a
plus d’élément, "next()" lève une exception "StopIteration" qui
indique à la boucle de l’instruction "for" de se terminer. Cet exemple
montre comment tout cela fonctionne :

   >>> s = 'abc'
   >>> it = iter(s)
   >>> it
   <iterator object at 0x00A1DB50>
   >>> it.next()
   'a'
   >>> it.next()
   'b'
   >>> it.next()
   'c'
   >>> it.next()
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       it.next()
   StopIteration

Une fois compris les mécanismes de gestion des itérateurs, il est
simple d’ajouter ce comportement à vos classes. Définissez une méthode
"__iter__()", qui retourne un objet disposant d’une méthode "next()".
Si la classe définit elle-même la méthode "next()", alors "__iter__()"
peut simplement renvoyer "self"

   class Reverse:
       """Iterator for looping over a sequence backwards."""
       def __init__(self, data):
           self.data = data
           self.index = len(data)

       def __iter__(self):
           return self

       def next(self):
           if self.index == 0:
               raise StopIteration
           self.index = self.index - 1
           return self.data[self.index]

   >>> rev = Reverse('spam')
   >>> iter(rev)
   <__main__.Reverse object at 0x00A1DB50>
   >>> for char in rev:
   ...     print char
   ...
   m
   a
   p
   s


9.10. Générateurs
=================

Les *générateur*s sont des outils simples et puissants pour créer des
itérateurs. Ils sont écrits comme des fonctions classiques mais
utilisent l’instruction "yield" lorsqu’ils veulent renvoyer des
données. À chaque fois qu’il est appelé par "next()", le générateur
reprend son exécution là où il s’était arrêté (en conservant tout son
contexte d’exécution). Un exemple montre très bien combien les
générateurs sont simples à créer :

   def reverse(data):
       for index in range(len(data)-1, -1, -1):
           yield data[index]

   >>> for char in reverse('golf'):
   ...     print char
   ...
   f
   l
   o
   g

Tout ce qui peut être fait avec des générateurs peut également être
fait avec des itérateurs basés sur des classes, comme décrit dans le
paragraphe précédent. Si qui fait que les générateurs sont si compacts
est que les méthodes "__iter__()" et "next()" sont créées
automatiquement.

Une autre fonctionnalité clé est que les variables locales ainsi que
le contexte d’exécution sont sauvegardés automatiquement entre les
appels. Cela simplifie d’autant plus l’écriture de ces fonctions et
rend leur code beaucoup plus lisible qu’avec une approche utilisant
des variables d’instance telles que "self.index" et "self.data".

En plus de la création automatique de méthodes et de la sauvegarde du
contexte d’exécution, les générateurs lèvent automatiquement une
exception "StopIteration" lorsqu’ils terminent leur exécution. La
combinaison de ces fonctionnalités rend très simple la création
d’itérateurs, sans plus d’effort que l’écriture d’une fonction
classique.


9.11. Expressions et générateurs
================================

Some simple generators can be coded succinctly as expressions using a
syntax similar to list comprehensions but with parentheses instead of
square brackets. These expressions are designed for situations where
the generator is used right away by an enclosing function.  Generator
expressions are more compact but less versatile than full generator
definitions and tend to be more memory friendly than equivalent list
comprehensions.

Exemples :

   >>> sum(i*i for i in range(10))                 # sum of squares
   285

   >>> xvec = [10, 20, 30]
   >>> yvec = [7, 5, 3]
   >>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
   260

   >>> from math import pi, sin
   >>> sine_table = dict((x, sin(x*pi/180)) for x in range(0, 91))

   >>> unique_words = set(word  for line in page  for word in line.split())

   >>> valedictorian = max((student.gpa, student.name) for student in graduates)

   >>> data = 'golf'
   >>> list(data[i] for i in range(len(data)-1,-1,-1))
   ['f', 'l', 'o', 'g']

-[ Notes ]-

[1] Il existe une exception : les modules disposent d’un attribut
    secret en lecture seule appelé "__dict__" 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.
    Évidemment, si vous l’utilisez, vous brisez l’abstraction de
    l’implémentation des espaces de noms. Il est donc réservé à des
    choses comme les débogueurs post-mortem.
