LangageClasses

[wiki: Classes] [#license ©]

PageOutline

Généralités

#!comment
Les types prédéfinis sont des classes ?
Les littéraux des objets ?


QUID DES METACLASSES ?


La création d’une nouvelle classe peut être considérée comme la création d’un nouveau type de données, défini par le développeur. Par convention, les noms de classe sont généralement typographié en « CamelCase ». Dans l’exemple ci-dessous, l’exécution de l’instruction class provoque la création d’un objet de type class, et lui assigne comme nom Vecteur. Comme n’importe quelle entité de Python, la classe créée est placée dans un module (ici main puisque l’instruction class est soumise directement à l’interpréteur).

#!div style="width:600px"


{{{
#!python
class Vecteur:
pass

print(Vecteur)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
<class 'main.Vecteur'>
</pre>

}}}

De même qu’un type prédéfini de Python (par exemple float ou list) possède de multiples valeurs (par exemple 3.14 ou []), une classe peut donner naissance à de multiples objets. On dit de chacun de ces objets qu’il est une « instance » de la classe. Pour créer une telle instance, on utilise le nom de la classe comme si c’était un nom de fonction (utilisation de parenthèses). Dans l’exemple ci-dessous, la première instruction provoque la création d’une instance de type Vecteur, et lui assigne comme nom v. La deuxième instruction créera une deuxième instance nommée w.

#!div style="width:600px"


{{{
#!python
v = Vecteur()
w = Vecteur()
print(v,w)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
<main.Vecteur object at ...> <main.Vecteur object at ...>
</pre>

}}}

#!comment
Il est possible de mettre des parenthèses après le nom de la classe, mais ceci est tout à fait optionnel : class Name(): est tout aussi correct que class Name:.

Données membres

Tout comme un nombre flottant est composé de sous-parties (mantisse, exposant, signe…), une instance est appelée à contenir des données nommées, accessibles individuellement par l’opérateur . . Les données membres peuvent être lues, créées et modifiées à tout moment et par n’importe qui.

#!div style="width:600px"


{{{
#!python
v.x, v.y, v.z = 1, 2, 3
print(v.x,v.y,v.z)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
1 2 3
</pre>
#!python
del v.z
print(v.x,v.y,v.z)
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
AttributeError: 'Vecteur' object has no attribute 'z'
</pre>

}}}

Méthodes

Imaginons ci-dessous que l’on souhaite doter nos nouveaux vecteurs d’un calcul de norme et d’une transformation de type homothétie. Pour uniformiser, on définit également une fonction pour initialiser les attributs d’une instance, et une fonction d’affichage :

#!div style="width:600px"


{{{
#!python
class Vecteur:
pass

def init(v,x=0,y=0):
v.x = x
v.y = y

def norme(v):
x2 = v.x**2
y2 = v.y**2
return (x2+y2)**(1./2.)

def homothetie(v,n):
v.x = v.x * n
v.y = v.y * n

def affiche(v):
print(v.x,v.y)

v = Vecteur()
init(v,3,-4)
print(norme(v))


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
5.0
</pre>
#!python
homothetie(v,3)
affiche(v)
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
9 -12
</pre>

}}}

On peut être rapidement confronté à des conflits de noms avec d’autres fonctions similaires pour d’autres classes, et on aimerait que nos nouvelles fonctions ne soient utilisées qu’avec notre nouvelle classe Vecteur. Pour ce faire, nous allons en faire des « méthodes » en les insérant dans la classe.

Une méthode est une fonction « attachée » à une classe particulière. La définition des méthodes est faite à l’intérieur de la définition de la classe.

Dans la définition d’une méthode, le premier paramètre désigne l’instance courante sur laquelle on veut appliquer la méthode. Pour exécuter une méthode sur une instance particulière, on utilise l’opérateur « . », comme pour les données membres, puis des parenthèses pour signifier qu’il s’agit d’une fonction. L’instance à gauche du « . » est alors passée à la méthode en tant que premier paramètre.

#!div style="width:600px"


{{{
#!python
class Vecteur:
def init(self,x=0,y=0):
self.x = x
self.y = y
def norme(self):
x2 = self.x**2
y2 = self.y**2
return (x2+y2)**(1./2.)
def homothetie(self,n):
self.x = self.x * n
self.y = self.y * n
def affiche(self):
print(self.x,self.y)

v = Vecteur()
v.init(3,-4)
print(v.norme())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
5.0
</pre>
#!python
v.homothetie(3)
v.affiche()
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
9 -12
</pre>

}}}

Encapsulation

Données membres et méthodes sont collectivement appelés « attributs ». On place généralement les données dans les instances (et elles varient d’une instance à l’autre), et les méthodes dans la classe.

En programmation orientée objet orthodoxe, il est préconisé d’interdire l’accès direct aux données par les codes clients extérieurs, et de forcer ces clients à passer par l’intermédiaire de méthodes de lecture (qu’on appelle « getters ») et de méthodes de modification (qu’on appelle « setters »).

Python ne permet pas vraiment d’interdire l’accès aux données, mais seulement de les masquer. Pour ce faire, donnez leur un nom qui commence par __. les données sont alors dites « pseudo-privées ».

Dans l’exemple ci-dessous, les attributs x et y de Vecteur ont été rendus pseudo-privés, la méthode init() fait office de « setter » global (méthode pour changer les valeurs de x et y), et les méthodes getx() et gety() font office de « getters ».

#!div style="width:600px"


{{{
#!python
class Vecteur:

def init(self,u=0,v=0):
self.__x = u
self.__y = v

def getx(self):
return self.__x

def gety(self):
return self.__y

v = Vecteur()
v.init(3, -4)
print(v.getx(),v.gety())


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
3 -4
</pre>
#!python
print(v.x,v.y)
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
...AttributeError: 'Vecteur' object has no attribute 'x'
</pre>
#!python
print(v.x,v.y)
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
...AttributeError: 'Vecteur' object has no attribute '__x'
</pre>

}}}

Documentation

Les « docstrings » habituelles fonctionnent avec les classes.

#!div style="width:600px"


{{{
#!python
class MaClasse:
"Documentation de MaClasse"
def ma_methode():
"documentation de ma_methode()"
pass

print(MaClasse.doc)


#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
Documentation de MaClasse
</pre>
#!python
print(MaClasse.ma_methode.doc)
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
documentation de ma_methode()
</pre>
#!python
help(MaClasse)
#!html
<pre  style="margin-left:22px;margin-right:22px;border: 1px solid lightgoldenrodyellow;padding:10px;background-color:lightyellow">
Help on class MaClasse in module main:


class MaClasse(builtin.object)
| Documentation de MaClasse
|
| Methods defined here:
|
| ma_methode()
| documentation de ma_methode()
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| dict
| dictionary for instance variables (if defined)
|
| weakref
| list of weak references to the object (if defined)
</pre>


}}}

#!comment


Exercice

Écrire une classe qui permettra de gérer un fichier (dont le nom sera passé au constructeur). Elle devra comporter plusieurs méthodes qui réaliseront des opérations sur le fichier :

  • count : renvoie le nombre de lignes, de mots et de lettres.
  • head(n) : renvoie les n premières lignes.
  • tail(n) : renvoie les n dernières lignes.
  • find(str) : afficher toutes les lignes contenant la chaine str.
  • replace(str1, str2) : afficher à l’écran toutes les lignes du fichier, en remplaçant chaque occurrence de str1 par str2.
  • tout ce qui pourra vous passer par la tête (mais qui ne modifie pas le fichier lui-même).

Il faudra veiller à ne pas charger en mémoire tout le contenu du fichier, ni à le conserver ouvert trop longtemps.

Travaux Pratiques

Reprenez comme point de départ la solution des travaux pratiques de la demi-journée précédente (calculatrice).

1. On veut transformer la fonction analyse() de telle sorte qu’elle retourne une instance de la classe Expression, contenant deux données membres : opList, liste des opérateurs, et numList la liste des nombres.

  • Placez la définition de classe dans le même fichier que analyse().
  • Dotez la classe Expression des méthodes suivantes :
  • init() : pour affecter des valeurs aux attributs opList et numList.
  • paires() : retourne une liste de paires (opérateur,nombre), chaque paire étant constituée du énième élément de opList et du enième élément correspondant dans la liste numList (ce que fait la fonction zip()).
  • display() : affichage le contenu de l’expression.
  • On pourra alors réécrire la fonction calcule() ainsi :
#!python


def calcule(chaine):
global res
expression = analyse(chaine)
expression.display()
for op, num in expression.paires():
if op=='=': res = num
else:
fct = operateurs.operations[op]
res = fct(res,num)
print(res)


1. Comme le code client n’a plus besoin d’accéder directement aux attributs opList et numList, profitez-en pour rendre ces attributs pseudo-privés.
1. Crééz une classe Calculatrice, dotée d’une variable res initialisée à 0 par une méthode init(), et d’une méthode calcule() qui évalue une instance d’Expression et stocke le résultat dans res (en plus de l’afficher).
1. Dotez la classe Expression d’une constante PI. Modifiez Expression.init() afin qu’elle reconnaisse la valeur PI, et essayez d’évaluer l’expression 2*PI avec votre calculatrice.

© David Chamont, Laboratoire Leprince-Ringuet (LLR) / IN2P3 / CNRS, Ecole polytechnique / Université Paris-Saclaybr
© Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution – Partage dans les Mêmes Conditions 4.0 International [=#license]


Attachments