Une bonne représentation d’objet change vite la façon dont on débogue un projet Python. La méthode spéciale __repr__ sert à produire une chaîne précise, lisible et, quand c’est possible, exploitable dans l’interpréteur. Dans cet article, je montre à quoi elle sert vraiment, comment elle se distingue de str(), comment l’écrire proprement et quels pièges éviter dans un backend ou un script de production.
Ce qu’il faut retenir avant d’implémenter une représentation d’objet
- repr() vise une représentation technique, utile pour le debug et le shell interactif.
- str() et print() cherchent une sortie plus naturelle pour l’humain.
- Une bonne représentation contient le nom de la classe et les attributs qui comptent vraiment.
- Je masque les données sensibles et j’évite tout calcul lourd dans cette méthode.
- Les
dataclassgénèrent souvent une__repr__correcte automatiquement. - Pour les gros objets,
reprlibetpprintsont souvent plus adaptés qu’un affichage brut.
À quoi sert vraiment __repr__ en Python
La documentation Python officielle décrit repr() comme la représentation « officielle » d’un objet. En pratique, je m’en sers surtout pour trois cas: lire un objet dans le shell, comprendre un log, et inspecter rapidement un état interne sans écrire de code de diagnostic supplémentaire.
L’idée n’est pas de produire un texte élégant, mais un texte sans ambiguïté. Si je peux, je fais en sorte que la chaîne ressemble à une expression Python crédible, ou au moins à une description suffisamment précise pour reconstruire mentalement l’objet. Quand une classe ne définit pas sa propre méthode, Python retombe sur l’implémentation de base, souvent peu parlante pour un humain.
Autre détail utile: dans l’interpréteur interactif, une expression affichée seule est représentée avec repr(). C’est pour ça qu’un objet bien conçu accélère la lecture du shell, du debugger et des traces de test. Le sujet devient plus clair dès qu’on compare ce comportement à celui de str().

repr(), str() et print() ne répondent pas au même besoin
La confusion la plus fréquente vient de là: __repr__ et __str__ ne servent pas la même intention. La première doit aider à identifier l’objet sans effort. La seconde doit aider à le lire rapidement. print(), lui, appelle surtout str(), puis retombe sur repr() si la classe n’a pas défini de représentation conviviale.
| Mécanisme | Rôle | Usage typique | Ce que j’en attends |
|---|---|---|---|
repr(obj) |
Représentation technique | Debug, logs, shell | Précision, stabilité, absence d’ambiguïté |
obj.__repr__() |
Implémentation de cette représentation | Classes métier, objets de données | Nom de classe + attributs pertinents |
str(obj) |
Version lisible pour l’humain | Affichage utilisateur, messages simples | Clarté et concision |
print(obj) |
Affichage standard | Console, scripts, debug rapide | Une sortie utile sans surcharger le code appelant |
Je garde aussi en tête un point pratique: dans une f-string, !r force l’usage de repr(). C’est souvent la meilleure option quand je veux afficher une valeur sans perdre les guillemets ni masquer la forme réelle des données. La documentation Python rappelle d’ailleurs que repr() vise une représentation pensée pour l’interprétation, alors que str() vise une lecture plus naturelle.
Une fois cette différence bien posée, on peut écrire une méthode utile sans la surcharger ni la confondre avec une sortie utilisateur.
Écrire une représentation utile et sans surprise
Quand je rédige une __repr__, je pars d’une règle simple: si je la lis dans trois mois, elle doit encore m’aider. Ça veut dire peu de bruit, pas de calcul coûteux, pas de dépendance cachée à une requête réseau ou à l’état d’un autre service. Cette méthode doit rester sûre à appeler dans un logger, un debugger ou un REPL.
Les règles que je garde
- J’affiche le nom de la classe dès le début.
- Je garde seulement les attributs qui aident vraiment à identifier l’objet.
- J’utilise
!rpour les chaînes et les valeurs qui doivent rester non ambiguës. - Je masque tout ce qui est sensible: mot de passe, token, secret, clé API.
- Je vise une sortie compacte, souvent sur une seule ligne.
- Je n’introduis aucun effet de bord: pas d’I/O, pas de requête base de données, pas d’appel HTTP.
Lire aussi : Python filter() - Quand l'utiliser et ses meilleures alternatives
Un exemple simple
class User:
def __init__(self, user_id, email, is_active=True):
self.user_id = user_id
self.email = email
self.is_active = is_active
def __repr__(self):
return (
f"User(user_id={self.user_id!r}, "
f"email={self.email!r}, "
f"is_active={self.is_active!r})"
)
def __str__(self):
return f"{self.email} ({'actif' if self.is_active else 'inactif'})"Ici, __repr__ reste technique et détaillée, tandis que __str__ est plus douce à lire. C’est une séparation que j’aime bien dans les services backend, parce qu’elle me permet d’avoir une sortie fiable pour le debug sans imposer le même format aux journaux applicatifs ou aux messages de réponse.
Le point clé, c’est que la représentation doit être cohérente avec la nature de l’objet. Un modèle métier, un objet de config ou un payload de sérialisation n’appellent pas exactement la même densité d’information.
Exemples concrets avec une classe, une dataclass et des objets standards
Dans Python moderne, je ne code pas toujours __repr__ à la main. Les dataclass génèrent automatiquement une représentation très correcte dans beaucoup de cas. La documentation Python indique que repr est activé par défaut, ce qui en fait un bon réflexe pour les objets de données simples.
from dataclasses import dataclass, field
@dataclass
class Order:
order_id: int
customer: str
total: float
secret_note: str = field(repr=False)Avec cette définition, l’objet s’affiche de manière lisible sans exposer secret_note. C’est exactement le genre de détail que je veux garder sous contrôle dans une application réelle: je veux que l’outil aide au diagnostic, pas qu’il divulgue des informations qui n’ont rien à faire dans un log.
| Approche | Quand je la choisis | Atout principal | Limite à garder en tête |
|---|---|---|---|
Classe avec __repr__ manuel |
Objet métier, format spécifique, données sensibles | Contrôle total | À maintenir à la main |
@dataclass |
Objets de transport, configuration, structures simples | Génération automatique cohérente | Moins de finesse si la sortie doit être très personnalisée |
| Objets standards du langage | Listes, dictionnaires, tuples, nombres, chaînes | Représentation déjà fiable | Pas de logique métier spécifique |
Dans un backend, je pars souvent sur @dataclass pour des objets de transport entre couches, puis je passe à une méthode manuelle si j’ai besoin d’exclure un champ, de normaliser une valeur ou de rendre la lecture plus parlante. C’est rarement un débat théorique: c’est surtout une question de lisibilité et de maîtrise des données affichées.
On voit déjà qu’une bonne représentation ne se résume pas à écrire une jolie chaîne. Elle dépend aussi des erreurs qu’on évite autour d’elle.
Les erreurs qui rendent la sortie trompeuse
J’ai vu les mêmes dérives revenir dans beaucoup de projets. Elles ne cassent pas toujours le code, mais elles rendent le debug inutilement pénible. C’est souvent à ce moment-là que les logs deviennent bavards sans être informatifs.
- Retourner autre chose qu’une chaîne: Python attend un
str, pas un objet arbitraire. - Appeler une base de données ou une API dans
__repr__: la méthode devient lente et imprévisible. - Afficher des secrets: un token exposé dans un log est presque toujours une mauvaise surprise.
- Mettre trop d’attributs: la sortie devient longue, donc moins lisible.
- Confondre affichage humain et sortie technique: on finit avec une représentation jolie mais peu utile.
- Oublier les objets imbriqués ou récursifs: certaines structures peuvent devenir difficiles à lire si on ne contrôle pas la profondeur.
Ma règle est simple: si la méthode doit m’aider à diagnostiquer un bug à 2 h du matin, elle ne doit pas dépendre d’un service externe ni d’un état fragile. Elle doit rester disponible, courte et fiable, même dans des situations dégradées.
Quand les objets deviennent volumineux ou trop profonds, je ne force pas la main à __repr__; j’utilise plutôt des outils dédiés.
Quand reprlib et pprint deviennent plus adaptés
Pour les grosses structures, une représentation complète peut devenir contre-productive. C’est là que la bibliothèque standard propose deux aides pratiques. reprlib limite la taille de la sortie, ce qui est très utile sur des listes profondes, des graphes imbriqués ou des objets très verbeux. De son côté, pprint réorganise l’affichage pour le rendre plus lisible, avec une largeur par défaut de 80 caractères.
| Outil | Ce qu’il apporte | Quand je l’utilise |
|---|---|---|
repr() |
Représentation complète et standard | Objets simples, débogage ciblé |
reprlib.repr() |
Sortie tronquée pour éviter les pavés | Structures longues, consoles de debug |
pprint.pformat() |
Affichage plus lisible sur plusieurs lignes | Inspection manuelle de structures complexes |
Je préfère pprint quand j’explore un dictionnaire ou une structure de configuration très large à la main. Je préfère reprlib quand je veux une version compacte dans un log ou un message d’erreur. Les deux outils évitent de transformer la console en mur de texte.
Pour moi, c’est surtout un rappel de méthode: je n’essaie pas de faire porter à __repr__ tout le poids de la lisibilité. Je choisis l’outil selon le contexte, ce qui donne souvent un code plus simple à maintenir.
La règle simple que je recommande avant de livrer une classe
Si je dois résumer mon approche, je dirais ceci: une bonne __repr__ sert d’abord le diagnostic, ensuite la lecture, et seulement en dernier lieu l’esthétique. Pour une classe métier, je veux voir ce qu’elle est et ce qui la distingue. Pour une classe de données, je veux une sortie fiable, stable et suffisamment courte pour être utile dans un log.
- Je garde
__repr__pour le détail technique. - J’ajoute
__str__si j’ai besoin d’une version plus conviviale. - Je masque les données sensibles au lieu de compter sur la discipline des utilisateurs du code.
- J’évite les effets de bord et les calculs lourds.
- Je teste la sortie dans le shell avant de considérer la classe comme prête.
Dans un projet Python bien tenu, cette petite méthode fait gagner du temps partout: dans les tests, dans les logs, dans le shell et dans les revues de code. C’est un détail en apparence, mais dans un backend réel, ce détail change souvent la vitesse à laquelle on comprend un problème.