Quand utiliser self en Python ?

Quand utiliser self en Python ?

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 !

Publié le 03 juin 2020 par Thibault
paceTemps de lecture estimé : 10 minutes

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 de comprendre !

Une classe pour les instancier tous

Imaginons que tu sois fan de cyclisme et que tu veuilles créer un programme pour gérer ta dépendance dévorante au short moulant :

class Velo:
    roues = 2 

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

velo = Velo(marque="Peugeot", prix=500, poids=100)

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 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.

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 :

class Velo:
    roues = 2 

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

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)

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. On pourrait donc utiliser Patrick à la place de self et ça fonctionnerait de la même façon.

En Python, on utilise le mot self par convention mais tu peux faire comme tu veux (on ne te le conseille pas, même si on aime bien Patrick 😉) !

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 fais, Python s'en charge tout seul 😉

# Utiliser la méthode rouler sur l'instance
velo_01.rouler()
# Revient au même que de passer par la classe et de passer l'instance à la méthode
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.