Les méthodes, la recette
Les attributs sont des variables propres à notre objet, qui servent à le caractériser.
Les méthodes sont plutôt des actions, comme nous l'avons vu dans la partie précédente, agissant sur l'objet.
Par exemple, la méthode append() de la classe list permet d'ajouter un élément dans l'objet list manipulé.

Pour créer nos premières méthodes, nous allons modéliser… un tableau. Un tableau noir, oui c'est très bien.

Notre tableau va posséder une surface (un attribut) sur laquelle on pourra écrire, que l'on pourra lire et effacer.
Pour créer notre classe TableauNoir et notre attribut surface, vous ne devriez pas avoir de problème :

class TableauNoir:
    """Classe définissant une surface sur laquelle on peut écrire,
     que l'on peut lire et effacer, par jeu de méthodes. L'attribut modifié
     est 'surface'    """
     def __init__(self):
     """Par défaut, notre surface est vide"""
         self.surface = ""


Nous avons déjà créé une méthode, aussi vous ne devriez pas être trop surpris par la syntaxe que nous allons voir.
Notre constructeur est en effet une méthode, elle en garde la syntaxe. Nous allons donc écrire notre méthode ecrire pour commencer.

class TableauNoir:
     """Classe définissant une surface sur laquelle on peut écrire,
     que l'on peut lire et effacer, par jeu de méthodes. L'attribut modifié
     est 'surface'    """
     def __init__(self):
         """Par défaut, notre surface est vide"""
          self.surface = ""
     def ecrire(self, message_a_ecrire):
         """Méthode permettant d'écrire sur la surface du tableau.
         Si la surface n'est pas vide, on saute une ligne avant de rajouter
         le message à écrire"""
         if self.surface != "":
             self.surface += "\n"
         self.surface += message_a_ecrire


Passons aux tests :

>>> tab = TableauNoir()
>>> tab.surface
''
>>> tab.ecrire("Coooool ! Ce sont les vacances !")
>>> tab.surface
"Coooool ! Ce sont les vacances !"
>>> tab.ecrire("Joyeux Noël !")
>>> tab.surface
"Coooool ! Ce sont les vacances !\nJoyeux Noël !"
>>> print(tab.surface)
Coooool ! Ce sont les vacances !
Joyeux Noël !
>>>



Notre méthode ecrire se charge d'écrire sur notre surface, en rajoutant un saut de ligne pour séparer chaque message.

On retrouve ici notre paramètre self. Il est temps de voir un peu plus en détail à quoi il sert.

Il est temps de voir en détail à quoi sert le paramètre self.


Le paramètre self
Dans nos méthodes d'instance, qu'on appelle également des méthodes d'objet, on trouve dans la définition ce paramètre self.
L'heure est venue de comprendre ce qu'il signifie.

Une chose qui a son importance : quand vous créez un nouvel objet, ici un tableau noir, les attributs de l'objet sont propres à l'objet créé.
C'est logique : si vous créez plusieurs tableaux noirs, ils ne vont pas tous avoir la même surface. Donc les attributs sont contenus dans l'objet.

En revanche, les méthodes sont contenues dans la classe qui définit notre objet. C'est très important. Quand vous tapez tab.ecrire(…),
Python va chercher la méthode ecrire non pas dans l'objet tab, mais dans la classeTableauNoir.

>>> tab.ecrire
< bound method TableauNoir.ecrire of <__main__.TableauNoir object at 0x00B3F3F0>>
>>> TableauNoir.ecrire
< function ecrire at 0x00BA5810>
>>> help(TableauNoir.ecrire)
Help on function ecrire in module __main__:
ecrire(self, message_a_ecrire)
Méthode permettant d'écrire sur la surface du tableau.
Si la surface n'est pas vide, on saute une ligne avant de rajouter
le message à écrire.
>>> TableauNoir.ecrire (tab, "essai")
>>> tab.surface
'essai'
>>>


Comme vous le voyez, quand vous tapez tab.ecrire(…), cela revient au même que si vous écrivez TableauNoir.ecrire (tab, …).
Votre paramètre self, c'est l'objet qui appelle la méthode. C'est pour cette raison que vous modifiez la surface de l'objet en appelant self.surface.

Pour résumer, quand vous devez travailler dans une méthode de l'objet sur l'objet lui-même, vous allez passer par self.

Le nom self est une très forte convention de nommage. Je vous déconseille de changer ce nom.
Certains programmeurs, qui trouvent qu'écrire self à chaque fois est excessivement long, l'abrègent en une unique lettres.
Évitez ce raccourci. De manière générale, évitez de changer le nom. Une méthode d'instance travaille avec le paramètre self.

N'est-ce pas effectivement plutôt long de devoir toujours travailler avec self à chaque fois qu'on souhaite faire appel à l'objet ?

Cela peut le sembler, oui. C'est d'ailleurs l'un des reproches qu'on fait au langage Python.
Certains langages travaillent implicitement sur les attributs et méthodes d'un objet sans avoir besoin de les appeler spécifiquement.
Mais c'est moins clair et cela peut susciter la confusion. En Python, dès qu'on voit self, on sait que c'est un attribut ou une méthode interne à l'objet qui va être appelé.

Bon, voyons nos autres méthodes. Nous devons encore coder lire qui va se charger d'afficher notre surface et effacer qui va effacer le contenu de notre surface.
Si vous avez compris ce que je viens d'expliquer, vous devriez écrire ces méthodes sans aucun problème, elles sont très simples.
Sinon, n'hésitez pas à relire, jusqu'à ce que le déclic se fasse.

class TableauNoir:
     """Classe définissant une surface sur laquelle on peut écrire,
     que l'on peut lire et effacer, par jeu de méthodes. L'attribut modifié
     est 'surface' """
     def __init__(self):
         """Par défaut, notre surface est vide"""
         self.surface = ""
     def ecrire(self, message_a_ecrire):
         """Méthode permettant d'écrire sur la surface du tableau.
         Si la surface n'est pas vide, on saute une ligne avant de rajouter
         le message à écrire"""
         if self.surface != "":
             self.surface += "\n"
         self.surface += message_a_ecrire
     def lire(self):
         """Cette méthode se charge d'afficher, grâce à print,
          la surface du tableau"""
         print(self.surface)
     def effacer(self):
         """Cette méthode permet d'effacer la surface du tableau"""
         self.surface = ""


Et encore une fois, le code de test :


>>> tab = TableauNoir()
>>> tab.lire()
>>> tab.ecrire("Salut tout le monde.")
>>> tab.ecrire("La forme ?")
>>> tab.lire()
Salut tout le monde.
La forme ?
>>> tab.effacer()
>>> tab.lire()
>>>



Et voilà ! Avec nos méthodes bien documentées, un petit coup de help(TableauNoir) et vous obtenez une belle description de l'utilité de votre classe.
C'est très pratique, n'oubliez pas les docstrings.

Méthodes de classe et méthodes statiques

Comme on trouve des attributs propres à la classe, on trouve aussi des méthodes de classe, qui ne travaillent pas sur l'instance self mais sur la classe même.
C'est un peu plus rare mais cela peut être utile parfois. Notre méthode de classe se définit exactement comme une méthode d'instance,
à la différence qu'elle ne prend pas en premier paramètre self(l'instance de l'objet) mais cls(la classe de l'objet).

En outre, on utilise ensuite une fonction built-in de Python pour lui faire comprendre qu'il s'agit d'une méthode de classe, pas d'une méthode d'instance.

class Compteur:
     """Cette classe possède un attribut de classe qui s'incrémente à chaque
     fois que l'on crée un objet de ce type"""
     objets_crees = 0      # Le compteur vaut 0 au départ
     def __init__(self):
         """À chaque fois qu'on crée un objet, on incrémente le compteur"""
         Compteur.objets_crees += 1
    def combien (cls) :     """Méthode de classe affichant combien
                                        d'objets ont été créés"""
        print("Jusqu'à présent ,{} objets ont été créés ".format(cls.objets_crees))     combien= classmethod(combien)

Voyons le résultat :

Compteur.combien()
>>>Jusqu'à présent, 0 objets ont été créés.
a = Compteur()
Compteur.combien()
>>>Jusqu'à présent, 1 objets ont été créés.
b = Compteur()
Compteur.combien()
>>>Jusqu'à présent, 2 objets ont été créés.

Résultat :

Une méthode de classe prend en premier paramètre non pas self mais cls. Ce paramètre contient la classe (ici Compteur).
Notez que vous pouvez appeler la méthode de classe depuis un objet instancié sur la classe. Vous auriez par exemple pu écrire a.combien().
Enfin, pour que Python reconnaisse une méthode de classe, il faut appeler la fonction classmethod qui prend en paramètre la méthode que l'on veut convertir
et renvoie la méthode convertie.

Si vous êtes un peu perdus, retenez la syntaxe de l'exemple. La plupart du temps, vous définirez des méthodes d'instance comme nous l'avons vu
plutôt que des méthodes de classe.

On peut également définir des méthodes statiques. Elles sont assez proches des méthodes de classe sauf qu'elles ne prennent aucun premier paramètre, ni self ni cls.
Elles travaillent donc indépendamment de toute donnée, aussi bien contenue dans l'instance de l'objet que dans la classe.

Voici la syntaxe permettant de créer une méthode statique.
On ne veut pas vous surcharger d'informations et on vous laisse faire vos propres tests si cela vous intéresse :

class Test:
     """Une classe de test tout simplement"""
     def afficher():
          """Fonction chargée d'afficher quelque chose"""
          print("On affiche la même chose.")
          print("peu importe les données de l'objet ou de la classe.")
     afficher = staticmethod(afficher)


Si vous vous emmêlez un peu avec les attributs et méthodes de classe, ce n'est pas bien grave.
Retenez surtout les attributs et méthodes d'instance, c'est essentiellement sur ceux-ci que l'on s'est attardé et c'est ceux que vous retrouverez la plupart du temps.

Rappel : les noms de méthodes encadrés par deux soulignés de part et d'autre sont des méthodes spéciales.
Ne nommez pas vos méthodes ainsi. Nous découvrirons plus tard ces méthodes particulières. Exemple de nom de méthode à éviter : __mamethode__.

Un peu d'introspection

Eh bien… le terme d'introspection, reconnaissons-le, fait penser à quelque chose de plutôt abstrait.
Pourtant, vous allez très vite comprendre l'idée qui se cache derrière :
Python propose plusieurs techniques pour explorer un objet, connaître ses méthodes ou attributs.

Quel est l'intérêt ? Quand on développe une classe, on sait généralement ce qu'il y a dedans, non ?

En effet. L'utilité, à notre niveau, ne saute pas encore aux yeux. Et c'est pour cela qu'on ne va pas trop m'attarder dessus.
Si vous ne voyez pas l'intérêt, contentez-vous de garder dans un coin de votre tête les deux techniques que nous allons voir.
Arrivera un jour où vous en aurez besoin ! Pour l'heure donc, voyons plutôt l'effet :

La fonction dir()
La première technique d'introspection que nous allons voir est la fonction dir().
Elle prend en paramètre un objet et renvoie la liste de ses attributs et méthodes.

class Test:
     """Une classe de test tout simplement"""
     def __init__(self):
         """On définit dans le constructeur un unique attribut"""
         self.mon_attribut = "ok"
     def afficher_attribut(self):
         """ Méthode affichant l'attribut 'mon_attribut '"""
         print("Mon attribut est {0}.".format(self.mon_attribut))

# Créons un objet de la classe Test
un_test = Test()
un_test.afficher_attribut()
Mon attribut est ok.

dir(un_test)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '_
_setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'afficher_attribut', 'mon_attribut']



La fonction dir() renvoie une liste comprenant le nom des attributs et méthodes de l'objet qu'on lui passe en paramètre.
Vous pouvez remarquer que tout est mélangé, c'est normal : pour Python, les méthodes, les fonctions, les classes, les modules sont des objets.
Ce qui différencie en premier lieu une variable d'une fonction, c'est qu'une fonction est exécutable (callable).
La fonction dir se contente de renvoyer tout ce qu'il y a dans l'objet, sans distinction.

Euh, c'est quoi tout cela ? On n'a jamais défini toutes ces méthodes ou attributs !
Non, en effet. Nous verrons plus loin qu'il s'agit de méthodes spéciales utiles à Python.

L'attribut spécial __dict__
Par défaut, quand vous développez une classe, tous les objets construits depuis cette classe posséderont un attribut spécial __dict__.
Cet attribut est un dictionnaire qui contient en guise de clés les noms des attributs et, en tant que valeurs, les valeurs des attributs.

Voyez plutôt :

>>> un_test = Test()
>>> un_test.__dict__
{'mon_attribut': 'ok'}
>>>


Pourquoi « attribut spécial » ?
C'est un attribut un peu particulier car ce n'est pas vous qui le créez, c'est Python.
Il est entouré de deux signes soulignés __ de part et d'autre, ce qui traduit qu'il a une signification pour Python et n'est pas un attribut « standard ».
Vous verrez plus loin dans ce cours des méthodes spéciales qui reprennent la même syntaxe.

Peut-on modifier ce dictionnaire ?
Vous le pouvez. Sachez qu'en modifiant la valeur de l'attribut, vous modifiez aussi l'attribut dans l'objet.

>>> un_test.__dict__["mon_attribut"] = "Faux"
>>> un_test.afficher_attribut()
Mon attribut est Faux.
>>>



De manière générale, ne faites appel à l'introspection que si vous avez une bonne raison de le faire et évitez ce genre de syntaxe.
Il est quand même plus propre d'écrire objet.attribut = valeur que objet.__dict__[nom_attribut] = valeur.

Nous n'irons pas plus loin dans ce chapitre. Vous découvrirez dans la suite de ce cours l'utilité des deux méthodes que l'on vous a montrées.

En résumé

On définit une classe en suivant la syntaxe class NomClasse:.
Les méthodes se définissent comme des fonctions, sauf qu'elles se trouvent dans le corps de la classe.
Les méthodes d'instance prennent en premier paramètre self, l'instance de l'objet manipulé.
On construit une instance de classe en appelant son constructeur, une méthode d'instance appelée __init__.
On définit les attributs d'une instance dans le constructeur de sa classe, en suivant cette syntaxe : self.nom_attribut = valeur.