La vérité sur self

En Python, self est à l'origine de nombreux problèmes chez les développeurs qui s'attaquent à la programmation orientée objet, essayons de démystifier ça !

Image de profil de l'auteur
Maxime DIONY
Partager:

Lorsque j'ai commencé à m'intéresser sérieusement à Python et à la Programmation Orientée Objet, il y a un truc que je n'arrivais pas à saisir : self 

Au début, je me contentais de l'ajouter à l'intérieur de mes méthodes et devant chacun de mes attributs sans comprendre ce qu'il se passait en arrière-plan parce que mon programme fonctionnait pas trop mal.

Quelle erreur !

C'est très important que tu saisisses bien à quoi ce self fait référence et pourquoi il est utile lorsque tu développes en Python.

C'est un concept clé de la Programmation Orientée Objet que tu te dois te maîtriser parfaitement !

🐍 Une classe pour les instancier tous

Imaginons que tu sois fan de cyclisme et que tu veuilles créer un programme pour gérer ton addiction dévorante du short moulant :

class Velo:

    roues = 2 

    def __init__(self, marque, prix, poids):
        self.marque = marque
        self.prix = prix
        self.poids = poids

Je vais commencer par te rappeler la différence entre une variable d'instance et une variable de classe histoire que ce soit bien clair dans ton esprit :

Une variable de classe est une variable qui est partagée par toutes les instances de ta classe.

Dans notre classe Velo, la variable roues est une variable de classe. Ce qui est plutôt logique car tous les vélos ou presque roulent sur deux roues et pas plus à moins que tu vives en Asie du Sud-Est et que tu voyages régulièrement en tuk-tuk.

On les définit à l'intérieur de la classe mais en dehors de toutes les autres méthodes !

À l'intérieur d'une classe, on ne parle plus de fonctions mais de méthodes.

De l'autre côté, on a les variables d'instances dont les valeurs diffèrent d'une instance à une autre.

Dans notre exemple, les variables self.marque, self.prix et self.poids sont des variables d'instance. Et comme tu le vois, elles sont définies à l'intérieur de la méthode __init__.

En Python, cette méthode spéciale est ce qu'on appelle un initialiseur. Elle est exécutée automagiquement à chaque fois que tu instancies un nouvel objet à partir de ta classe.

Tu dois donc y placer tout ce qui semble inhérent à l'objet que tu souhaites créer. Et petite précision, tu dois obligatoirement nommer cette méthode de cette façon, avec deux tirets du bas puis le mot init et encore deux autres tirets du bas.

Voyons maintenant ce que ça donne si je crée deux instances de ma classe Velo et que j'essaie d'accéder à ces variables :

velo_01 = Velo("btwin", 250, 15)
velo_02 = Velo("rockrider", 170, 12)

print(velo_01.roues) # 2
print(velo_02.roues) # 2 

print(velo_01.marque) # btwin
print(velo_02.marque) # rockrider' 

Comme je te le disais, toutes les instances ont accès aux variables de classe, c'est pourquoi mes deux vélos ont chacun deux roues sans que je n'ai rien eu besoin de spécifier.

Par contre, tu vois que chaque vélo possède sa propre marque car ce sont deux instances différentes !

Je t'invite à tester ce code chez toi pour que tu comprennes bien 🙂

🐍 À quoi sert le self ?

Lorsque j'ai créé ma méthode __init__, tu as remarqué qu'en plus des paramètres, j'avais ajouté le mot self. Je l'ai également utilisé pour définir mes variables d'instance. 

Mais pourquoi ?

En fait, self est utilisé pour représenter l'instance de la classe ! C'est grâce à lui que je peux donc accéder aux attributs et aux méthodes de ma classe.

On va se créer une méthode d'instance pour que tu visualises :

class Velo:

    roues = 2

    def __init__(self, marque, prix, poids):
        self.marque = marque
        self.prix = prix
        self.poids = poids

    def rouler(self):
        print(f"Wouh, ça roule mieux avec un vélo {self.marque} !")

velo_01 = Velo("btwin", 250, 15)
velo_02 = Velo("rockrider", 170, 12)

print(velo_01.roues) # 2
print(velo_02.roues) # 2

print(velo_01.marque) # btwin
print(velo_02.marque) # rockrider
 
velo_01.rouler() # Wouh, ça roule mieux avec un vélo btwin !
velo_02.rouler() # Wouh, ça roule mieux avec un vélo rockrider !

J'ai créé une méthode rouler dans ma classe Velo et j'ai indiqué comme premier paramètre self :

def rouler(self):
    print(f"Wouh, ça roule mieux avec un vélo {self.marque} !")

De ce fait, je peux appeler cette méthode rouler sur toutes les instances que j'ai créées.

Sache que tu n'es pas obligé d'utiliser explicitement le mot self car en réalité ce n'est pas un mot réservé en Python. Si tu viens d'un autre langage de programmation comme JavaScript, tu as peut-être l'habitude d'utiliser this.

En Python, on utilise le mot self par convention mais tu peux faire comme tu veux (on ne te le conseille pas 😉) !

Par contre, il y a une règle que tu dois absolument respecter : Lorsque tu définis une méthode d'instance, ton instance, c'est-à-dire self (ou le mot que tu as choisi pour représenter ton instance) doit toujours être passé en première position, c'est obligatoire alors fais y attention 🙏

🐍 Le secret du self

Tu as dû le remarquer mais dans le dernier exemple j'ai appelé ma méthode rouler sans passer d'argument et pourtant le code a fonctionné. J'arrive à afficher un message avec la marque du vélo de l'instance en cours.

Tu serais en droit de te demander comment ma méthode rouler fait pour savoir qu'il faut qu'elle utilise les attributs de cette instance et pas d'une autre ?

Je pense que tu l'as compris, c'est le rôle de self !

Lorsque je fais ça :

velo_01.rouler()

Voilà ce qu'il se passe en coulisses :

Velo.rouler(velo_01)

L'interpréteur Python fait automatiquement la conversion pour toi !

D'une part, ça rend ton code plus esthétique et de l'autre, ça te facilite la vie quand tu veux faire hériter ta classe d'autres classes. C'est une notion que je ne détaillerais pas ici sinon on serait reparti pour trois paragraphes et je pense que tu en as déjà eu assez !

Et évidemment si tu oublies d'ajouter self lorsque tu définis ta méthode d'instance, tu auras un petit problème quand tu voudras l'utiliser :

def rouler():
    print(f"Wouh, ça roule mieux avec un vélo {self.marque} !")

velo_01.rouler() #TypeError: rouler() takes 0 positional arguments but 1 was given 

👉 #TypeError: rouler() takes 0 positional arguments but 1 was given

Cette erreur, elle est souvent incompréhensible pour les gens qui ne sont pas habitués à la programmation orientée objet.

Python nous dit qu'un argument a été envoyé à la méthode rouler (1 was given), pourtant quand on regarde notre code, on n'envoie aucun argument 🤨

velo_01.rouler()

Mais comme on vient de le voir plus haut, en arrière-plan, Python utilise ta classe pour exécuter la méthode rouler, et passe l'instance automatiquement en premier argument. Donc un argument est bien envoyé à la méthode rouler, c'est juste que tu ne le vois pas et ce n'est pas toi qui le fait, Python s'en charge tout seul 😉

Velo.rouler(velo_01)

Tu es donc obligé de mettre self en premier paramètre dans la définition de la méthode rouler pour pouvoir l'utiliser avec ton instance velo_01.

Il est possible de créer des méthodes statiques qui permettent de contourner un peu les règles en n'ayant pas besoin de passer self en premier paramètre. On en parlera plus en détail dans un autre article.

🐍 Conclusion

Voilà, j'espère que tu comprends mieux comment utiliser self et son importance pour Python !

Si ce n'est pas le cas, ce que je pourrais comprendre car ce n'est vraiment pas un sujet évident de prime abord, je t'invite à laisser un commentaire pour en discuter ou même à venir nous rejoindre sur le Discord de Docstring.

Abonne-toi à l'infolettre,

pour recevoir en exclusivité les prochains articles.

Articles populaires

Tout le monde les as lus, au moins deux fois !