Définition de __repr__ en Python
__repr__ est une méthode spéciale (aussi appelée dunder method ou magic method) en Python qui permet de définir la représentation officielle d'un objet sous forme de chaîne de caractères. Son nom vient de l'anglais representation. Lorsque vous appelez la fonction native repr() sur un objet ou que vous inspectez un objet directement dans un interpréteur Python, c'est la méthode __repr__ qui est invoquée en arrière-plan.
L'objectif principal de __repr__ est de fournir une représentation non ambiguë de l'objet, idéalement suffisamment précise pour qu'un développeur puisse recréer l'objet à partir de cette chaîne. Si vous souhaitez approfondir vos connaissances sur Python et maîtriser les méthodes spéciales, nous vous recommandons de suivre notre formation complète sur Python qui couvre en détail la programmation orientée objet.
La règle d'or : __repr__ est destinée aux développeurs, tandis que __str__ est destinée aux utilisateurs finaux. Si vous ne devez implémenter qu'une seule des deux méthodes, choisissez toujours __repr__.
Comment fonctionne __repr__ ?
Chaque objet en Python hérite de la class de base object, qui fournit une implémentation par défaut de __repr__. Cette implémentation par défaut retourne une chaîne contenant le nom de la classe et l'adresse mémoire de l'objet, ce qui n'est généralement pas très utile :
class Voiture:
def __init__(self, marque, modele):
self.marque = marque
self.modele = modele
ma_voiture = Voiture("Peugeot", "308")
print(repr(ma_voiture))
# Résultat : <__main__.Voiture object at 0x7f8b8c0d4a90>Comme vous pouvez le constater, cette sortie par défaut ne nous dit rien sur le contenu réel de l'objet. C'est pourquoi il est fortement recommandé de définir votre propre méthode __repr__ dans vos classes personnalisées.
Implémentation personnalisée de __repr__
Voici comment implémenter correctement __repr__ pour notre classe Voiture :
class Voiture:
def __init__(self, marque, modele, annee):
self.marque = marque
self.modele = modele
self.annee = annee
def __repr__(self):
return f"Voiture(marque='{self.marque}', modele='{self.modele}', annee={self.annee})"
ma_voiture = Voiture("Peugeot", "308", 2023)
print(repr(ma_voiture))
# Résultat : Voiture(marque='Peugeot', modele='308', annee=2023)Remarquez que nous utilisons ici une f-string pour formater la sortie. L'idée est que la chaîne retournée ressemble à l'appel du constructeur qui permettrait de recréer l'objet.
Différence entre __repr__ et __str__
La distinction entre __repr__ et __str__ est une source fréquente de confusion pour les développeurs. Voici un tableau récapitulatif pour bien comprendre leurs différences :
| Caractéristique | __repr__ | __str__ |
|---|---|---|
| Public cible | Développeurs | Utilisateurs finaux |
| Objectif | Représentation non ambiguë | Représentation lisible |
| Appelée par | repr(), interpréteur interactif | str(), print() |
| Fallback | Implémentation par défaut de object | Utilise __repr__ si non définie |
| Convention | Devrait ressembler à du code Python valide | Libre, axée sur la lisibilité |
Point important : si vous définissez __repr__ mais pas __str__, Python utilisera automatiquement __repr__ comme fallback lors de l'appel à print ou str(). L'inverse n'est pas vrai.
Voici un exemple concret illustrant les deux méthodes :
class Produit:
def __init__(self, nom, prix):
self.nom = nom
self.prix = prix
def __repr__(self):
return f"Produit(nom='{self.nom}', prix={self.prix})"
def __str__(self):
return f"{self.nom} - {self.prix}€"
produit = Produit("Clavier mécanique", 89.99)
# __repr__ est appelée dans l'interpréteur ou via repr()
print(repr(produit)) # Produit(nom='Clavier mécanique', prix=89.99)
# __str__ est appelée par print() ou str()
print(produit) # Clavier mécanique - 89.99€
Quand __repr__ est-elle appelée ?
La méthode __repr__ est invoquée dans plusieurs contextes que vous devez connaître :
1. Dans l'interpréteur interactif
Lorsque vous tapez le nom d'un objet directement dans l'interpréteur Python (ou dans un notebook Jupyter), c'est __repr__ qui est appelée :
>>> produit = Produit("Souris", 49.99)
>>> produit
Produit(nom='Souris', prix=49.99)
2. Dans les conteneurs (listes, dictionnaires, etc.)
Quand un objet se trouve à l'intérieur d'un conteneur comme une list ou un dict, Python utilise __repr__ pour l'afficher, même si vous appelez print sur le conteneur :
produits = [
Produit("Clavier", 89.99),
Produit("Souris", 49.99),
Produit("Écran", 299.99)
]
print(produits)
# [Produit(nom='Clavier', prix=89.99), Produit(nom='Souris', prix=49.99), Produit(nom='Écran', prix=299.99)]C'est un comportement extrêmement important à comprendre : même si vos objets ont un __str__ très lisible, c'est __repr__ qui sera utilisée lorsqu'ils sont à l'intérieur d'une liste ou d'un dictionnaire.
3. Avec la fonction repr()
La fonction native repr() appelle directement __repr__ :
produit = Produit("Webcam", 59.99)
repr_str = repr(produit)
print(repr_str) # Produit(nom='Webcam', prix=59.99)
4. Dans les f-strings avec le flag !r
Vous pouvez forcer l'utilisation de __repr__ dans une f-string grâce au flag !r :
produit = Produit("Casque", 129.99)
print(f"Debug : {produit!r}")
# Debug : Produit(nom='Casque', prix=129.99)
print(f"Affichage : {produit!s}")
# Affichage : Casque - 129.99€
Exemples avancés
__repr__ avec l'héritage
Lorsque vous travaillez avec l'héritage de classes, il est important de définir __repr__ correctement pour chaque niveau de la hiérarchie :
class Animal:
def __init__(self, nom, espece):
self.nom = nom
self.espece = espece
def __repr__(self):
return f"Animal(nom='{self.nom}', espece='{self.espece}')"
class Chien(Animal):
def __init__(self, nom, race):
super().__init__(nom, espece="Chien")
self.race = race
def __repr__(self):
return f"Chien(nom='{self.nom}', race='{self.race}')"
chien = Chien("Rex", "Berger Allemand")
print(repr(chien)) # Chien(nom='Rex', race='Berger Allemand')
__repr__ avec les dataclasses
Les dataclass en Python génèrent automatiquement une méthode __repr__ pour vous, ce qui est l'un de leurs grands avantages :
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
z: float = 0.0
point = Point(1.5, 2.3, 4.0)
print(repr(point)) # Point(x=1.5, y=2.3, z=4.0)Comme vous le voyez, la dataclass génère un __repr__ parfaitement conforme aux conventions. Vous pouvez toutefois désactiver ce comportement avec @dataclass(repr=False) si vous souhaitez définir votre propre implémentation.
__repr__ avec les namedtuples
De la même manière, les namedtuple fournissent automatiquement un __repr__ informatif :
from collections import namedtuple
Couleur = namedtuple("Couleur", ["rouge", "vert", "bleu"])
c = Couleur(255, 128, 0)
print(repr(c)) # Couleur(rouge=255, vert=128, bleu=0)
__repr__ et eval()
L'une des conventions les plus importantes de __repr__ est que, dans la mesure du possible, la chaîne retournée devrait être un code Python valide permettant de recréer l'objet :
class Vecteur:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vecteur({self.x}, {self.y})"
def __eq__(self, other):
return self.x == other.x and self.y == other.y
v1 = Vecteur(3, 4)
v2 = eval(repr(v1)) # Recrée l'objet à partir de sa repr
print(v1 == v2) # True
print(repr(v2)) # Vecteur(3, 4)Attention : n'utilisez jamais eval() sur des données non fiables en production. Cet exemple est purement illustratif de la convention __repr__.
Bonnes pratiques
Voici les règles essentielles à suivre lorsque vous implémentez __repr__ dans vos classes :
1. Toujours implémenter __repr__
Même si vous définissez __str__, implémentez toujours __repr__. C'est la méthode de représentation la plus fondamentale et elle sert de fallback pour __str__.
2. Retourner du code Python valide si possible
La convention veut que __repr__ retourne une chaîne qui, passée à eval(), recréerait l'objet :
# Bonne pratique
def __repr__(self):
return f"MaClasse(param1={self.param1!r}, param2={self.param2!r})"
# Mauvaise pratique
def __repr__(self):
return f"Instance de MaClasse"Notez l'utilisation de !r dans la f-string pour les attributs de type chaîne. Cela garantit que les guillemets sont inclus dans la sortie, rendant la représentation plus fidèle.
3. Utiliser le format <...> quand eval() n'est pas possible
Si votre objet est trop complexe pour être recréé via eval(), utilisez la convention des chevrons :
class ConnexionBDD:
def __init__(self, hote, port, base):
self.hote = hote
self.port = port
self.base = base
self._connexion = None # État interne complexe
def __repr__(self):
return f""
conn = ConnexionBDD("localhost", 5432, "mon_app")
print(repr(conn)) #
4. Inclure les informations essentielles
La représentation doit contenir suffisamment d'informations pour identifier l'objet et le distinguer des autres instances. Incluez au minimum les attributs définis dans le constructeur (__init__ via def).
5. Garder la représentation concise
Évitez d'inclure des attributs internes ou des détails d'implémentation qui ne sont pas pertinents pour le débogage :
# Trop verbeux
def __repr__(self):
return (f"Utilisateur(id={self.id}, nom='{self.nom}', email='{self.email}', "
f"date_creation={self.date_creation}, derniere_connexion={self.derniere_connexion}, "
f"nb_connexions={self.nb_connexions}, preferences={self.preferences})")
# Juste ce qu'il faut
def __repr__(self):
return f"Utilisateur(id={self.id}, nom='{self.nom}', email='{self.email}')"
__repr__ pour les types natifs de Python
Il est intéressant de noter que tous les types natifs de Python implémentent déjà __repr__ de manière cohérente :
# Chaînes de caractères
print(repr("Bonjour")) # 'Bonjour'
# Nombres
print(repr(42)) # 42
print(repr(3.14)) # 3.14
# Listes
print(repr([1, 2, 3])) # [1, 2, 3]
# Tuples
print(repr((1, 2))) # (1, 2)
# Dictionnaires
print(repr({"a": 1})) # {'a': 1}
# Ensembles
print(repr({1, 2, 3})) # {1, 2, 3}Vous remarquerez que pour les chaînes de caractères, repr() ajoute des guillemets autour de la valeur, ce qui la distingue clairement de str(). Cela est particulièrement utile pour le débogage lorsque vous travaillez avec des tuple, des sets ou des dict.
Questions fréquentes
Quelle est la différence entre __repr__ et __str__ en Python ?
__repr__ fournit une représentation technique et non ambiguë de l'objet, destinée aux développeurs. Elle doit idéalement retourner du code Python valide permettant de recréer l'objet. __str__, en revanche, fournit une représentation lisible et conviviale, destinée aux utilisateurs finaux. Si __str__ n'est pas définie, Python utilise __repr__ comme fallback. La convention recommande de toujours définir __repr__ en priorité.
Dois-je toujours implémenter __repr__ dans mes classes ?
Oui, c'est une bonne pratique fortement recommandée. Sans implémentation personnalisée, Python utilise la représentation par défaut de object qui affiche uniquement le nom de la classe et l'adresse mémoire, ce qui n'est pas utile pour le débogage. Si vous utilisez des dataclass ou des namedtuple, la méthode __repr__ est générée automatiquement, ce qui vous évite de l'écrire manuellement.
Pourquoi __repr__ est-elle utilisée à l'intérieur des listes et dictionnaires ?
Lorsque Python affiche le contenu d'un conteneur (list, dict, tuple, etc.), il utilise __repr__ et non __str__ pour chaque élément. Ce choix de conception s'explique par le fait que l'affichage d'un conteneur est généralement destiné au débogage, et Python privilégie donc la représentation non ambiguë. C'est pourquoi il est essentiel d'avoir un __repr__ informatif pour bien visualiser vos objets.
Comment apprendre à maîtriser les méthodes spéciales en Python ?
Les méthodes spéciales comme __repr__, __str__, __init__ ou encore __eq__ sont au cœur de la programmation orientée objet en Python. Pour les maîtriser en profondeur, nous vous recommandons de suivre notre formation dédiée à Python sur Believemy, qui couvre de manière progressive et pratique toutes ces notions essentielles, des bases jusqu'aux concepts avancés.