Découverte de la notion d'objet
Objets et POO sont au centre de la manière Python fonctionne. Vous n'êtes pas obligé d'utiliser la POO dans vos programmes - mais comprendre le concept est essentiel pour devenir plus qu'un débutant. Entre autres raisons parce que vous aurez besoin d'utiliser les classes et objets fournis par la librairie standard.
De plus, avant d'aborder la programmation d'interfaces graphiques qui utilisent abondamment les objet, des notions autour de la POO seront utiles.
La programmation en tant que telle est une matière relativement récente. Etonnament la programmation orientée objet remonte aussi loin que les années 1960. Simula est considéré comme le premier langage de programmation orienté objet.
Les années 1970 voient les principes de la programmation par objet se développent et prennent forme au travers notamment du langage Smalltalk
À partir des années 1980, commence l'effervescence des langages à objets : Objective C (début des années 1980, utilisé sur les plateformes Mac et iOS), C++ (C with classes) en 1983 sont les plus célèbres.
Les années 1990 voient l'âge d'or de l'extension de la programmation par objet dans les différents secteurs du développement logiciel, notemment grâce à l'émergence des systèmes d'exploitation basés sur une interface graphique (MacOS, Linux, Windows) qui font appel abondamment aux principes de la POO.
Nous verrons sur le prochain classeur comment une interface graphique peut se programmer au moyens d'objets (fenêtre, boutons, textes, champs de saisie etc...).
La programmation procédurale est celle que vous avez utilisé jusqu'à maintenant : cela consiste à diviser votre programme en blocs réutilisables appelés fonctions.
Vous essayez autant que possible de garder votre code en blocs modulaires, en décidant de manière logique quel bloc est appelé. Cela demande moins d’effort pour visualiser ce que votre programme fait. Cela rend plus facile la maintenance de votre code – vous pouvez voir ce que fait une portion de code. Le fait d’améliorer une fonction (qui est réutilisée) peut améliorer la performance à plusieurs endroits dans votre programme.
Vous avez des variables, qui contiennent vos données, et des fonctions. Vous passez vos variables à vos fonctions – qui agissent sur elles et peut-être les modifient. L'inteaction entre les variables et les fonctions n'est pas toujours simple à gérer comme on l'a vu dans le classeur précédent ! ou bien une variable est locale et n'est pas visible des autres fonction, ou bien une variable est globale et toutes les fonctions sont suceptibles d'y avoir accès.
On touche ici aux limites de la programmation procédurale, lorsque le nombre de fonctions et de variables devient important.
En Python les éléments de base de la programmation que nous avons rencontré comme les chaînes de caractères, ou les listes sont des objets. Ils possèdent des propriétés - variables qui stockent des valeurs - et des méthodes - fonctions qui agissent sur ces valeurs.
Voici un petit exemple d'objet qui vous est déjà familier :
liste=[3,5,4,2,8,5,4]
liste.sort()
liste
Ici nous avons fait appel à la méthode sort() de l'objet liste afin de trier notre liste.
Mais dans nos projets futurs, nous pouvons avoir envie de définir nos propres objets, c'est à dire d'enrichir la bibliothèque de types *built-in* standard de Python avec des objets que nous façonnerons selon nos besoins. C'est la qu'intervient la notion de classe.
En premier exemple, supposons que nous voulions travailler sur un logiciel de géométrie. Nous avons besoin d'un *objet* point qui est un nouveau type d'objet contenant deux informations :
Ces deux informations sont ce que nous appelons en POO des *attributs* ou des *propriétés*.
Assez de discours, créons notre classe :
class Point():
abscisse=0
ordonnee=0
Et c'est tout !!! nous avons créé une classe contenant deux *propriétés* une abscisse et une ordonnée toutes deux initialisées à 0.
Comment ça marche ?
print (Point.abscisse)
print (Point.ordonnee)
Point.abscisse=2
print (Point.abscisse)
Ca a l'air trè simple ! En réalité, nous allons vite être limité si nous n'utilisons que cette classe. En effet, nous créé un objet classe Point qui contient deux informations. Mais dans notre logiciel de géométrie, nous voulons créer plusieurs points !!
C'est le moment de parler de la notion *d'instance*. Une instance est un objet que nous créons en mémoire à partir d'une classe. Voici comment :
p1=Point()
p2=Point()
p1.abscisse=2
p2.ordonnee=3
print (p1.abscisse,p1.ordonnee)
print (p2.abscisse,p2.ordonnee)
Nous y voila ! J'ai donc à présent la possibilité de créer autant de points que je veux. Il faut bien distinguer la notion de *classe* et la notion *d'instance* :
Pour bien comprendre ce phénomène, prenons une comparaison avec le monde des contructeurs automobile : Lorsqu'un contructeur va sortir une nouvelle voiture, il ne va pas immédiatement produire en série plusieurs millions de véhicules. Il va tout d'abord élaborer un *prototype* :
C'est ce travail que nous réaliserons lorsque nous construirons notre *classe. Construire une classe c'est construire un prototype unique*.
Une fois notre prototype terminé, notre constructeur va passer à la *production en série. Il va créer des millions d'instances* de notre prototype qui sont les voitures *créées en série à partir de notre prototype. Chaque instance* pourra être personnalisé à partir de notre prototype : en effet chaque nouvelle voiture possèdera sa propre couleur qui n'est pas forcément celle de notre prototype, possèdera des options spécifique (gps, toit ouvrant etc...).
Retenez donc cette comparaison :
Nous avons créé notre objet point qui se caractérise par deux *propriétés* : abscisse et ordonnée. Mais si ce n'était que cela, pourquoi ne pas utiliser un tuple ! Nous allons donc enrichir notre classe (le prototype servant de modèle pour créer nos points) en y ajoutant des fonctions uniques : les *méthodes*.
Nous nous intéressons par exemple à la distance séparant notre point de l'origine du repère. Nous souhaiterions que notre objet point possède une *méthode* pour nous renvoyer cette information. Une *méthode* n'est autre qu'une *fonction* intégrée à un objet. Voici comment procéder. Nous allons modifier notre *classe* :
from math import sqrt # On a besoin de la racine carrée !
class Point():
abscisse=0
ordonnee=0
def distanceAZero (self):
return sqrt (self.abscisse**2+self.ordonnee**2)
Regardons le résultat :
p1=Point()
p1.abscisse=3
p1.ordonnee=4
print(p1.distanceAZero())
Et voila ! notre *objet* point commence à prendre tournure : il possède
Cette méthode est une fonction encapsulée dans notre objet qui agit sur ses propriétés et effecture le travail demandé. Revenons sur la déclaration de cette méthode :
Une méthode se déclare comme une c=fonction classique à l'intérieur de la classe à ceci près qu'elle prend toujours *en premier argument l'instance sur laquelle elle agit. Par convention, nous nommons cette instance self*.
Nous voyons sur l'exemple de la distanceAZero l'avantage de disposer de cette information d'instance : nous voulons que la méthode agisse sur l'*instance* depuis laquelle elle a été appelée et non sur les propriétés de la classe (le prototype). La variable *self* nous permettra de connaître l'instance sur laquelle nous travaillons.
Reste à décrire la syntaxe un peu étrange de cette fonction : Si *self* est le premier argument, pourquoi ne le trouve t-on pas lors de l'appel de la fonction distanceAZero() ? Voici l'explication.
En réalité, nous devrions passer l'appel à la méthode de cette manière :
print(Point.distanceAZero(p1))
Ainsi nous voyons bien que distanceAZero accepte bien l'instance sur laquelle elle agit en premier paramètre et que c'est une fonciton intégrée à la classe *Point*. Néanmoins cette syntaxe est très lourde ! Imaginez taper la ligne suivante à la place de
liste.append(5)
list.append(liste,'autre syntaxe')
# Et pourtant cela foncitonne !
print(liste)
Dans la pratique, une méthode sera toujours appelée depuis une instance et le premier paramètre sera omis puisque il est donnée justement par l'instance qui appelle. Syntaxiquement, les deux formes
Point.distanceAZero(p1)
et
p1.distanceAZero()
sont équivalentes. Nous utiliserons systématiquement la seconde forme.
Vous allez enrichir la classe *Point* en ajoutant
Attention, je rappelle qu'une méthode prend *toujours* en premier argument *self*.
from math import sqrt # On a besoin de la racine carrée !
# Redéfinissez votre classe
# YOUR CODE HERE
raise NotImplementedError()
Pour tester votre classe, validez la cellule suivante. La réponse doit être :
La distance AB = 5.0
Out[...]:5.0
p1=Point()
p1.abscisse=2
p1.ordonnee=3
p2=Point()
p2.abscisse=-1
p2.ordonnee=7
p2.nom='B'
assert p1.distance(p2)==5.0
On peut améliorer un peu le comportement de notre classe en initialisant de manière plus propre les différentes propriétés. En effet, pour le moment, pour créer un point avec le bon nom et les coordonnées souhaitées, nous avons besoin de 4 lignes ! p1=Point() p1.abscisse=2 p1.ordonnee=3 p1.nom='P'
On peut faire beaucoup mieux en *surchargeant* la méthode *init()* qui est une méthode spéciale appelée automatiquement lors de la création d'ue instance. Cette méthode prend
class Point():
def __init__(self,x,y,nom):
self.abscisse=x
self.ordonnee=y
self.nom=nom
def distanceAZero (self):
return sqrt (self.abscisse**2+self.ordonnee**2)
def distance(self, p):
d=sqrt((self.abscisse-p.abscisse)**2+(self.ordonnee-p.ordonnee)**2)
print ("La distance ",self.nom+p.nom,"=",d)
return d
Regardons comment créer notre point :
p1=Point(2,3,'A')
p2=Point(-1,7,'B')
p1.distance(p2)
C'est quand même bien mieux ! Mais tout n'est pas parfait. Observez ce qui se passe si je veux afficher les coordonnées d'un point. Je peux avoir envie de faire cela :
print(p1)
# beark
Il existe une autre méthode magique - en réalité, il y en a environs 80 - permettant de redéfinir le comportement des opérateurs intégrés à Python. Vous allez créer une méthode nommée *str()* qui
Pour construire votre chaîne, vous pourrez utiliser la concaténation de chaines de caractères au moyen de l'opérateur +. Regardez l'exemple :
x,y=2,3
chaine="A"+str(x)+"; etc..."
# etc... vous voyez le principe
print(chaine)
# A vous de jouer
# YOUR CODE HERE
raise NotImplementedError()
# Et voila la magie qui s'opère !
p1=Point(2,3,'A')
print(p1)
assert p1.__str__()=='A(2;3)'
Et voila, bien venue dans le monde merveilleux des objets.
En seconde partie, nous allons prendre un exemple plus sofistiqué sur les polynomes pour approfondir les notions que nous avons introduites dans ce classeur.
A bientôt !