Python : Comprendre __str__ et __repr__ pour vos classes

Apprenez a creer des representations lisibles de vos objets Python avec les methodes __str__ et __repr__. Guide pratique avec exemples, bonnes pratiques et pieges a eviter.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 7 min read
Python : Comprendre __str__ et __repr__ pour vos classes

Comprendre les representations de chaines de Python : guide complet pour les developpeurs

Introduction

En tant que developpeur Python, vous avez certainement deja rencontre cette situation frustrante : vous creez un objet personnalise et essayez de l’afficher pour le debugger, mais au lieu de voir une representation claire et informative, vous obtenez quelque chose comme <__main__.MonObjet object at 0x7f8b8c0d2e80>. Cette representation cryptique ne vous aide en rien a comprendre l’etat de votre objet.

Ce probleme est universel en programmation orientee objet, et Python offre une solution elegante grace aux methodes speciales __str__ et __repr__. Ces deux methodes, souvent confondues par les debutants, ont des roles distincts mais complementaires :

  • __str__ : Cree une representation lisible pour l’utilisateur final
  • __repr__ : Cree une representation technique pour le developpeur

Dans cet article, nous allons explorer en profondeur ces deux methodes, comprendre leurs differences, et apprendre a les implementer correctement dans vos classes. Vous decouvrirez egalement les bonnes pratiques a suivre et les pieges courants a eviter.

Pourquoi les representations de chaines sont-elles importantes ?

Avant de plonger dans le code, comprenons pourquoi les representations de chaines sont cruciales dans le developpement Python :

  1. Debugging facilite : Une bonne representation permet d’identifier rapidement l’etat d’un objet lors du debogage
  2. Logs comprehensibles : Les fichiers de logs deviennent lisibles et exploitables
  3. Tests plus clairs : Les messages d’erreur des tests unitaires sont plus informatifs
  4. Documentation vivante : Le code devient auto-documente grace a des representations explicites
  5. Experience utilisateur : Pour les applications interactives, les objets s’affichent de maniere comprehensible

La problematique

Supposons que vous ayez cree une classe Card qui represente une carte a jouer :

class Card:
    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

ace_of_spades = Card('Spades', 1)
four_of_clubs = Card('Clubs', 4)
six_of_hearts = Card('Hearts', 6)

my_hand = [ace_of_spades, four_of_clubs, six_of_hearts]

Lorsque vous essayez d’afficher la main sous forme de chaîne en utilisant print(my_hand), vous obtenez :

[<__main__.Card instance at 0x0000000002533788>,
 <__main__.Card instance at 0x00000000025B95C8>,
 <__main__.Card instance at 0x00000000025FF508>]

Au lieu de voir la main avec les cartes affichees sous forme de chaine, vous obtenez un long code hexadecimal. Ce comportement par defaut est rarement utile en pratique.

La solution (Partie 1) : la methode __str__

La methode __str__ est la premiere solution pour rendre vos objets lisibles. Elle est appelee automatiquement lorsque vous utilisez print() sur un objet ou lorsque vous convertissez un objet en chaine avec str().

Caracteristiques de __str__ :

  • Doit retourner une chaine de caracteres
  • Orientee vers l’utilisateur final (lisibilite)
  • Appelee par print(), str(), et les f-strings
class Card:
    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    def __str__(self):
        special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}
        card_name = special_names.get(self.pips, str(self.pips))
        return "%s of %s" % (card_name, self.suit)

Lorsque nous appelons print(ace_of_spades), nous obtenons maintenant :

Ace of Spades

Vous pouvez egalement utiliser __str__ avec les f-strings :

ace = Card('Spades', 1)
message = f"Vous avez tire : {ace}"
print(message)  # Vous avez tire : Ace of Spades

La solution (Partie 2) : la methode __repr__

Mais que se passe-t-il lorsque nous essayons d’afficher la main sous forme de chaine en utilisant print(my_hand) ? Nous obtenons encore une fois les codes hexadecimaux. Pourquoi ?

C’est parce que lorsqu’un objet est dans un conteneur (liste, dictionnaire, tuple, etc.), Python utilise __repr__ et non __str__ pour l’afficher. Cette methode est conçue pour fournir une representation technique, idealement une expression Python valide qui pourrait recreer l’objet.

Caracteristiques de __repr__ :

  • Doit retourner une chaine de caracteres
  • Orientee vers le developpeur (debugging)
  • Utilisee dans les conteneurs, le shell interactif, et par repr()
  • Idealement, devrait etre une expression Python valide
class Card:
    special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}

    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    def __str__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return "%s of %s (S)" % (card_name, self.suit)

    def __repr__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return "%s of %s (R)" % (card_name, self.suit)

Lorsque nous appelons print(my_hand), nous obtenons maintenant :

[Ace of Spades (R), 4 of Clubs (R), 6 of Hearts (R)]

Un exemple plus realiste avec __repr__

Dans la pratique professionnelle, __repr__ devrait idealement retourner une chaine qui permet de recreer l’objet :

class Card:
    special_names = {1:'Ace', 11:'Jack', 12:'Queen', 13:'King'}

    def __init__(self, suit, pips):
        self.suit = suit
        self.pips = pips

    def __str__(self):
        card_name = Card.special_names.get(self.pips, str(self.pips))
        return f"{card_name} of {self.suit}"

    def __repr__(self):
        return f"Card('{self.suit}', {self.pips})"

# Utilisation
ace = Card('Spades', 1)
print(str(ace))   # Ace of Spades
print(repr(ace))  # Card('Spades', 1)

# La representation repr peut etre evaluee pour recreer l'objet
ace_copy = eval(repr(ace))
print(ace_copy)   # Ace of Spades

Astuce : eviter la duplication avec un seul __repr__

Si vous ne definissez que __repr__, Python l’utilisera aussi pour __str__ si ce dernier n’est pas defini. C’est une technique courante pour eviter la duplication :

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 4)
print(p)       # Point(3, 4)
print(str(p))  # Point(3, 4)
print(repr(p)) # Point(3, 4)

Bonnes Pratiques

Voici les recommandations essentielles pour implementer __str__ et __repr__ correctement :

1. Toujours implementer __repr__

__repr__ est plus importante que __str__. Si vous ne devez en implementer qu’une seule, choisissez __repr__. Elle sert de fallback pour __str__ et est indispensable pour le debugging.

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    def __repr__(self):
        return f"User(username='{self.username}', email='{self.email}')"

2. Rendre __repr__ non ambigu

La representation __repr__ doit etre unique et identifiable. Incluez le nom de la classe et les attributs essentiels :

class Transaction:
    def __init__(self, amount, currency, timestamp):
        self.amount = amount
        self.currency = currency
        self.timestamp = timestamp

    def __repr__(self):
        return (f"Transaction(amount={self.amount}, "
                f"currency='{self.currency}', "
                f"timestamp='{self.timestamp}')")

3. Utiliser les f-strings modernes

Preferez les f-strings aux anciennes methodes de formatage pour plus de lisibilite :

# Moderne et lisible
def __repr__(self):
    return f"Card('{self.suit}', {self.pips})"

# Ancien style (eviter)
def __repr__(self):
    return "Card('%s', %d)" % (self.suit, self.pips)

4. Gerer les objets imbriques

Quand votre objet contient d’autres objets, utilisez leur repr() :

class Hand:
    def __init__(self, cards):
        self.cards = cards

    def __repr__(self):
        cards_repr = ', '.join(repr(card) for card in self.cards)
        return f"Hand([{cards_repr}])"

5. Limiter la longueur pour les grands objets

Pour les objets contenant beaucoup de donnees, tronquez la representation :

class DataFrame:
    def __init__(self, data):
        self.data = data

    def __repr__(self):
        n_rows = len(self.data)
        if n_rows > 5:
            return f"DataFrame({n_rows} rows, preview: {self.data[:3]}...)"
        return f"DataFrame({self.data})"

Pieges Courants

Evitez ces erreurs frequentes lors de l’implementation de ces methodes :

1. Ne pas retourner une chaine

__str__ et __repr__ DOIVENT retourner une chaine. Retourner un autre type provoque une erreur :

# MAUVAIS - provoque TypeError
class BadExample:
    def __repr__(self):
        return 42  # TypeError!

# BON - retourne une chaine
class GoodExample:
    def __repr__(self):
        return "42"

2. Confondre print et return

Ne confondez pas print() et return. Ces methodes doivent RETOURNER la chaine, pas l’afficher :

# MAUVAIS - affiche mais ne retourne rien
class BadExample:
    def __str__(self):
        print(f"Value: {self.value}")  # Mauvais!

# BON - retourne la chaine
class GoodExample:
    def __str__(self):
        return f"Value: {self.value}"

3. Oublier les conteneurs

N’oubliez pas que __repr__ est utilisee dans les conteneurs :

# Si vous n'implementez que __str__, les listes affichent le repr par defaut
items = [Card('Hearts', 1), Card('Clubs', 5)]
print(items)  # Utilisera __repr__, pas __str__

4. Representations trop verbeuses

Evitez les representations trop longues qui encombrent les logs :

# MAUVAIS - trop verbeux
def __repr__(self):
    return f"User(id={self.id}, username='{self.username}', email='{self.email}', created_at='{self.created_at}', updated_at='{self.updated_at}', is_active={self.is_active}, role='{self.role}', preferences={self.preferences})"

# BON - concis et informatif
def __repr__(self):
    return f"User(id={self.id}, username='{self.username}')"

5. Ne pas gerer les valeurs None

Pensez aux cas ou les attributs peuvent etre None :

class Profile:
    def __init__(self, name, bio=None):
        self.name = name
        self.bio = bio

    def __repr__(self):
        bio_repr = f"'{self.bio}'" if self.bio else "None"
        return f"Profile(name='{self.name}', bio={bio_repr})"

Resume des differences

Aspect__str____repr__
ObjectifLisibilite humaineDebugging technique
AudienceUtilisateur finalDeveloppeur
Appele parprint(), str(), f-stringsrepr(), conteneurs, shell
Format idealTexte naturelExpression Python valide
FallbackUtilise __repr__ si absentRepresentation par defaut

Conclusion

Maitriser __str__ et __repr__ est essentiel pour tout developpeur Python professionnel. Ces methodes transforment vos objets opaques en entites comprehensibles, facilitant enormement le debugging et la maintenance du code.

Points cles a retenir :

  • Implementez toujours __repr__ : C’est la methode la plus importante pour le debugging
  • Utilisez __str__ pour l’affichage utilisateur : Quand vous avez besoin d’une representation plus naturelle
  • Rendez __repr__ evaluable : Idealement, eval(repr(obj)) devrait recreer l’objet
  • Soyez concis mais informatif : Incluez les attributs essentiels sans surcharger
  • Testez les deux methodes : Verifiez le comportement dans les conteneurs et avec print()

En suivant ces principes, vous ecrirez du code Python plus maintenable et plus facile a debugger. Vos collegues (et votre futur vous) vous remercieront !

Prochaines etapes

Pour approfondir vos connaissances sur les methodes speciales Python :

  • Explorez les autres methodes “dunder” comme __eq__, __hash__, et __bool__
  • Decouvrez les dataclasses Python qui generent automatiquement __repr__
  • Apprenez a utiliser le module reprlib pour des representations personnalisees
  • Experimentez avec le decorateur @functools.total_ordering pour les comparaisons
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles