Définition de self en Python
En Python, self est une convention de nommage utilisée comme premier paramètre des méthodes d'instance dans une class. Il fait référence à l'instance courante de l'objet sur laquelle la méthode est appelée. Grâce à self, chaque objet peut accéder à ses propres attributs et méthodes, ce qui est au cœur même de la programmation orientée objet.
Contrairement à d'autres langages comme Java ou C++ où le mot-clé this est implicite, Python exige que self soit déclaré explicitement comme premier paramètre de chaque méthode d'instance. C'est l'une des particularités de Python qui surprend souvent les débutants, mais qui rend le code plus lisible et plus explicite — fidèle à la philosophie « Explicit is better than implicit » du Zen de Python.
Si vous souhaitez maîtriser pleinement la programmation orientée objet en Python, y compris l'utilisation de self, nous vous recommandons de suivre notre formation complète sur Python qui aborde ces concepts en profondeur.
Pourquoi self est-il nécessaire ?
Pour comprendre pourquoi self est indispensable, il faut d'abord comprendre comment Python gère les méthodes d'instance en interne. Lorsque vous appelez une méthode sur un objet, Python effectue une transformation automatique :
# Quand vous écrivez :
mon_objet.ma_methode(arg1, arg2)
# Python transforme en interne cet appel en :
MaClasse.ma_methode(mon_objet, arg1, arg2)Autrement dit, Python passe automatiquement l'instance (mon_objet) comme premier argument de la méthode. C'est pourquoi ce premier paramètre — que nous appelons self par convention — est obligatoire : il permet à la méthode de savoir sur quel objet elle travaille.
Sans self, une méthode n'aurait aucun moyen de distinguer les attributs d'une instance par rapport à une autre. Imaginons deux objets créés à partir de la même classe : chacun possède ses propres données, et c'est self qui permet à Python de les différencier.
Utilisation de base de self
Déclarer et accéder aux attributs d'instance
L'utilisation la plus courante de self se fait dans la méthode __init__ (le constructeur) pour initialiser les attributs de l'objet, puis dans les autres méthodes pour y accéder :
class Utilisateur:
def __init__(self, nom, email):
self.nom = nom # Attribut d'instance
self.email = email # Attribut d'instance
self.actif = True # Attribut avec valeur par défaut
def afficher_profil(self):
print(f"Nom : {self.nom}")
print(f"Email : {self.email}")
print(f"Actif : {self.actif}")
# Création de deux instances distinctes
alice = Utilisateur("Alice", "alice@example.com")
bob = Utilisateur("Bob", "bob@example.com")
alice.afficher_profil()
# Nom : Alice
# Email : alice@example.com
# Actif : True
bob.afficher_profil()
# Nom : Bob
# Email : bob@example.com
# Actif : TrueDans cet exemple, self.nom dans la méthode afficher_profil fait référence à l'attribut nom de l'instance sur laquelle la méthode est appelée. Quand on appelle alice.afficher_profil(), self vaut alice. Quand on appelle bob.afficher_profil(), self vaut bob.
Appeler d'autres méthodes via self
self ne sert pas uniquement à accéder aux attributs. Il permet aussi d'appeler d'autres méthodes de la même classe depuis une méthode :
class CompteBancaire:
def __init__(self, titulaire, solde=0):
self.titulaire = titulaire
self.solde = solde
self.historique = []
def deposer(self, montant):
self.solde += montant
self._enregistrer_operation("dépôt", montant)
def retirer(self, montant):
if montant > self.solde:
print("Solde insuffisant !")
return
self.solde -= montant
self._enregistrer_operation("retrait", montant)
def _enregistrer_operation(self, type_op, montant):
self.historique.append(f"{type_op} : {montant}€")
def afficher_historique(self):
print(f"Historique de {self.titulaire} :")
for operation in self.historique:
print(f" - {operation}")
print(f"Solde actuel : {self.solde}€")
compte = CompteBancaire("Alice", 1000)
compte.deposer(500)
compte.retirer(200)
compte.afficher_historique()
# Historique de Alice :
# - dépôt : 500€
# - retrait : 200€
# Solde actuel : 1300€Remarquez comment self._enregistrer_operation() est appelée depuis deposer() et retirer(). Grâce à self, chaque méthode peut accéder à l'ensemble des fonctionnalités de l'instance.
self et les différents types de méthodes
Il est essentiel de comprendre que self n'est utilisé que dans les méthodes d'instance. Python propose trois types de méthodes dans une classe, et chacune gère différemment la référence à l'objet :
| Type de méthode | Premier paramètre | Décorateur | Accès |
|---|---|---|---|
| Méthode d'instance | self | Aucun | Attributs et méthodes d'instance |
| Méthode de classe | cls | @classmethod | Attributs et méthodes de classe |
| Méthode statique | Aucun | @staticmethod | Aucun accès direct à la classe ou l'instance |
Voici un exemple qui illustre ces trois types :
class Produit:
tva = 0.20 # Attribut de classe
def __init__(self, nom, prix_ht):
self.nom = nom # Attribut d'instance
self.prix_ht = prix_ht # Attribut d'instance
# Méthode d'instance : utilise self
def prix_ttc(self):
return self.prix_ht * (1 + self.tva)
# Méthode de classe : utilise cls, pas self
@classmethod
def modifier_tva(cls, nouvelle_tva):
cls.tva = nouvelle_tva
# Méthode statique : ni self ni cls
@staticmethod
def est_prix_valide(prix):
return prix > 0
produit = Produit("Ordinateur", 1000)
print(produit.prix_ttc()) # 1200.0
print(Produit.est_prix_valide(-5)) # False
Produit.modifier_tva(0.055)
print(produit.prix_ttc()) # 1055.0self et l'héritage
L'un des aspects les plus puissants de self se manifeste dans le contexte de l'héritage. Lorsqu'une classe enfant hérite d'une classe parente, self fait toujours référence à l'instance réelle, même lorsqu'une méthode de la classe parente est exécutée :
class Animal:
def __init__(self, nom):
self.nom = nom
def se_presenter(self):
print(f"Je suis {self.nom} et je fais : {self.cri()}")
def cri(self):
return "..."
class Chien(Animal):
def cri(self):
return "Wouf !"
class Chat(Animal):
def cri(self):
return "Miaou !"
rex = Chien("Rex")
rex.se_presenter()
# Je suis Rex et je fais : Wouf !
felix = Chat("Félix")
felix.se_presenter()
# Je suis Félix et je fais : Miaou !Dans cet exemple, la méthode se_presenter() est définie dans Animal, mais quand elle appelle self.cri(), c'est la version de la sous-classe qui est appelée. C'est le principe du polymorphisme, rendu possible grâce à self.
Utiliser self avec super()
Lorsque vous surchargez la méthode __init__ dans une classe enfant, il est courant d'appeler le constructeur de la classe parente avec super() :
class Vehicule:
def __init__(self, marque, annee):
self.marque = marque
self.annee = annee
def description(self):
return f"{self.marque} ({self.annee})"
class Voiture(Vehicule):
def __init__(self, marque, annee, nb_portes):
super().__init__(marque, annee) # Appel du constructeur parent
self.nb_portes = nb_portes # Attribut spécifique
def description(self):
base = super().description()
return f"{base} - {self.nb_portes} portes"
ma_voiture = Voiture("Peugeot", 2023, 5)
print(ma_voiture.description())
# Peugeot (2023) - 5 portesIci, self reste cohérent tout au long de la chaîne d'héritage. Quand super().__init__(marque, annee) est appelé, self dans le constructeur de Vehicule fait toujours référence à l'instance de Voiture.
Différence entre self et les variables locales
Une confusion fréquente chez les débutants est la différence entre self.variable et une simple variable locale. Comprendre cette distinction est fondamental :
class Exemple:
def __init__(self, valeur):
self.valeur = valeur # Attribut d'instance : persiste
def calculer(self):
resultat = self.valeur * 2 # Variable locale : temporaire
self.dernier_calcul = resultat # Attribut d'instance : persiste
return resultat
def afficher(self):
print(f"Valeur : {self.valeur}")
print(f"Dernier calcul : {self.dernier_calcul}")
# print(resultat) # ERREUR : resultat n'existe pas ici !
obj = Exemple(10)
obj.calculer()
obj.afficher()
# Valeur : 10
# Dernier calcul : 20Retenez cette règle : tout ce qui est préfixé par self. est stocké dans l'instance et accessible depuis n'importe quelle méthode de cet objet. Les variables sans self. sont locales à la méthode et disparaissent à la fin de son exécution.
self n'est qu'une convention
Un point important à comprendre : self n'est pas un mot-clé réservé de Python. C'est une convention de nommage universellement adoptée par la communauté. Techniquement, vous pourriez utiliser n'importe quel autre nom :
# Cela fonctionne... mais ne le faites JAMAIS !
class MauvaiseIdee:
def __init__(ceci, nom):
ceci.nom = nom
def saluer(ceci):
print(f"Bonjour, {ceci.nom} !")
obj = MauvaiseIdee("Alice")
obj.saluer() # Bonjour, Alice !Bien que techniquement valide, utiliser un autre nom que self est considéré comme une très mauvaise pratique. La PEP 8 (guide de style officiel de Python) recommande explicitement l'utilisation de self. Ne pas respecter cette convention rend votre code confus pour les autres développeurs.
Erreurs courantes avec self
Oublier self dans la signature de la méthode
class Erreur1:
def __init__(self, nom):
self.nom = nom
def saluer(): # Oubli de self !
print(f"Bonjour, {self.nom}")
obj = Erreur1("Alice")
obj.saluer()
# TypeError: saluer() takes 0 positional arguments but 1 was givenPython essaie de passer l'instance comme premier argument, mais la méthode n'accepte aucun paramètre, d'où l'erreur.
Oublier self. pour accéder à un attribut
class Erreur2:
def __init__(self, nom):
self.nom = nom
def saluer(self):
print(f"Bonjour, {nom}") # Oubli de self. !
obj = Erreur2("Alice")
obj.saluer()
# NameError: name 'nom' is not definedSans le préfixe self., Python cherche une variable locale ou globale nommée nom, qui n'existe pas.
Confondre attribut de classe et attribut d'instance
class Erreur3:
compteur = 0 # Attribut de classe
def __init__(self, nom):
self.nom = nom
self.compteur += 1 # Crée un attribut d'instance, ne modifie PAS l'attribut de classe !
a = Erreur3("Alice")
b = Erreur3("Bob")
print(a.compteur) # 1 (attribut d'instance de a)
print(b.compteur) # 1 (attribut d'instance de b)
print(Erreur3.compteur) # 0 (attribut de classe inchangé !)
# Solution correcte :
class Correct:
compteur = 0
def __init__(self, nom):
self.nom = nom
Correct.compteur += 1 # Modifie l'attribut de classe directement
c = Correct("Alice")
d = Correct("Bob")
print(Correct.compteur) # 2Bonnes pratiques avec self
Voici les principales bonnes pratiques à suivre lorsque vous travaillez avec self en Python :
- Toujours utiliser le nom
self: ne jamais utiliser un autre nom, c'est la convention universelle de Python. - Initialiser tous les attributs dans __init__ : même si vous attribuez une valeur par défaut comme
None, déclarez tous les attributs dans le constructeur pour une meilleure lisibilité. - Ne pas surcharger
self: évitez de stocker trop de données dans l'instance. Si votre classe a des dizaines d'attributs, c'est un signe qu'elle devrait être découpée en plusieurs classes. - Utiliser des dataclass quand c'est approprié : pour les classes qui servent principalement à stocker des données, les dataclasses réduisent le code répétitif lié à
self. - Préfixer les méthodes privées avec un underscore : utilisez
self._methode_privee()pour signaler qu'une méthode est destinée à un usage interne. - Documenter les attributs avec des docstring : ajoutez une documentation claire pour décrire le rôle de chaque attribut d'instance.
Utilisation avancée : self avec les méthodes magiques
Python utilise intensivement self dans les méthodes spéciales (aussi appelées « dunder methods » pour double underscore). Ces méthodes permettent de personnaliser le comportement de vos objets :
class Vecteur:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vecteur({self.x}, {self.y})"
def __add__(self, autre):
return Vecteur(self.x + autre.x, self.y + autre.y)
def __eq__(self, autre):
return self.x == autre.x and self.y == autre.y
def __len__(self):
return int((self.x ** 2 + self.y ** 2) ** 0.5)
def __bool__(self):
return self.x != 0 or self.y != 0
v1 = Vecteur(3, 4)
v2 = Vecteur(1, 2)
v3 = v1 + v2
print(v3) # Vecteur(4, 6)
print(v1 == v2) # False
print(len(v1)) # 5
print(bool(Vecteur(0, 0))) # FalseDans chacune de ces méthodes, self fait référence à l'instance sur laquelle l'opération est effectuée. Par exemple, dans v1 + v2, self vaut v1 et autre vaut v2.
Questions fréquentes
Peut-on nommer le premier paramètre autrement que self ?
Oui, techniquement c'est possible car self n'est pas un mot-clé réservé de Python. Cependant, c'est une convention extrêmement forte respectée par toute la communauté et recommandée par la PEP 8. Utiliser un autre nom rendrait votre code difficile à lire et à maintenir. Ne le faites jamais dans du code professionnel ou partagé.
Quelle est la différence entre self et cls ?
self fait référence à une instance spécifique d'une classe et est utilisé dans les méthodes d'instance. cls fait référence à la classe elle-même et est utilisé dans les méthodes de classe décorées avec @classmethod. Avec self, vous accédez aux attributs d'instance ; avec cls, vous accédez aux attributs de classe.
Pourquoi Python ne rend-il pas self implicite comme this dans d'autres langages ?
C'est un choix de design de Python qui suit la philosophie « Explicit is better than implicit ». Rendre self explicite rend le code plus lisible et réduit les ambiguïtés. On sait immédiatement si on accède à un attribut d'instance ou à une variable locale. C'est aussi plus cohérent avec le fonctionnement interne de Python où les méthodes ne sont que des fonctions dans un espace de noms de classe.
Comment apprendre à bien utiliser self et la POO en Python ?
La meilleure façon d'apprendre à utiliser self correctement est de pratiquer la programmation orientée objet avec des projets concrets. Nous vous recommandons de suivre notre formation dédiée à Python qui vous guidera pas à pas dans la maîtrise des classes, de self, de l'héritage et de tous les concepts avancés de la POO.