Comment utiliser les méthodes __init__, __str__ et __repr__ en Python

PythonBeginner
Pratiquer maintenant

Introduction

Les fonctionnalités de programmation orientée objet (POO) de Python offrent des outils puissants pour créer des objets expressifs et personnalisés. Parmi ceux-ci, les méthodes spéciales __init__, __str__ et __repr__ jouent des rôles cruciaux dans la définition du comportement et de la représentation de vos objets Python.

Dans ce tutoriel, nous allons explorer ces méthodes spéciales, comprendre leurs objectifs et apprendre à les implémenter efficacement dans vos classes Python. À la fin de ce lab, vous serez capable de créer des objets Python plus intuitifs et conviviaux.

Création de classes avec la méthode __init__

La première méthode spéciale que nous allons explorer est __init__, qui est appelée lorsqu'un objet est créé. Cette méthode vous permet d'initialiser les attributs de votre objet.

Comprendre la méthode __init__

La méthode __init__, également connue sous le nom de constructeur, est automatiquement appelée lorsque vous créez une nouvelle instance d'une classe. Son objectif principal est de configurer l'état initial de l'objet en affectant des valeurs aux attributs.

Commençons par créer une classe Python simple avec la méthode __init__ :

  1. Ouvrez le terminal dans votre environnement LabEx.

  2. Naviguez vers le répertoire du projet :

    cd ~/project
  3. Créez un nouveau fichier Python appelé person.py en utilisant l'éditeur de code :

  4. Dans l'éditeur, ajoutez le code suivant à person.py :

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    ## Create a Person object
    person1 = Person("Alice", 30)
    
    ## Access the attributes
    print(f"Name: {person1.name}")
    print(f"Age: {person1.age}")
  5. Enregistrez le fichier.

  6. Exécutez le script Python :

    python3 person.py

Vous devriez voir la sortie suivante :

Name: Alice
Age: 30

Explication de la méthode __init__

Dans le code ci-dessus :

  • Nous avons défini une classe Person avec une méthode __init__ qui prend trois paramètres : self, name et age.
  • Le paramètre self fait référence à l'instance en cours de création et est automatiquement passé lorsqu'un objet est créé.
  • À l'intérieur de la méthode __init__, nous affectons les valeurs de name et age aux attributs de l'objet en utilisant self.name et self.age.
  • Lorsque nous créons un nouvel objet Person avec person1 = Person("Alice", 30), la méthode __init__ est automatiquement appelée.
  • Nous pouvons ensuite accéder aux attributs en utilisant la notation pointée : person1.name et person1.age.

Ajout de fonctionnalités supplémentaires

Améliorons notre classe Person en ajoutant une méthode pour calculer l'année de naissance en fonction de l'année en cours et de l'âge de la personne :

  1. Ouvrez person.py dans l'éditeur.

  2. Mettez à jour le code pour inclure une méthode de calcul de l'année de naissance :

    import datetime
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def birth_year(self):
            current_year = datetime.datetime.now().year
            return current_year - self.age
    
    ## Create a Person object
    person1 = Person("Alice", 30)
    
    ## Access the attributes and call the method
    print(f"Name: {person1.name}")
    print(f"Age: {person1.age}")
    print(f"Birth Year: {person1.birth_year()}")
  3. Enregistrez le fichier.

  4. Exécutez le script Python :

    python3 person.py

La sortie devrait maintenant inclure l'année de naissance calculée :

Name: Alice
Age: 30
Birth Year: 1993

(L'année de naissance réelle dépendra de l'année en cours lorsque vous exécuterez le script)

Vous avez maintenant créé une classe Python avec la méthode __init__ pour initialiser les attributs de l'objet et ajouté une méthode pour effectuer des calculs basés sur ces attributs.

Implémentation de la méthode __str__

Maintenant que nous comprenons comment créer une classe avec la méthode __init__, explorons une autre méthode spéciale appelée __str__. Cette méthode nous permet de définir comment un objet doit être représenté sous forme de chaîne de caractères.

Comprendre la méthode __str__

La méthode __str__ est appelée lorsque vous utilisez la fonction str() sur un objet ou lorsque vous imprimez un objet en utilisant la fonction print(). Elle doit renvoyer une représentation sous forme de chaîne de caractères lisible par l'homme de l'objet.

Mettons à jour notre classe Person pour inclure une méthode __str__ :

  1. Ouvrez person.py dans l'éditeur.

  2. Mettez à jour le code pour inclure une méthode __str__ :

    import datetime
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def birth_year(self):
            current_year = datetime.datetime.now().year
            return current_year - self.age
    
        def __str__(self):
            return f"{self.name}, {self.age} years old"
    
    ## Create a Person object
    person1 = Person("Alice", 30)
    
    ## Access the attributes and call the method
    print(f"Name: {person1.name}")
    print(f"Age: {person1.age}")
    print(f"Birth Year: {person1.birth_year()}")
    
    ## Use the __str__ method implicitly
    print("\nString representation of the object:")
    print(person1)
    
    ## Use the __str__ method explicitly
    print("\nExplicit string conversion:")
    print(str(person1))
  3. Enregistrez le fichier.

  4. Exécutez le script Python :

    python3 person.py

Vous devriez voir une sortie similaire à celle-ci :

Name: Alice
Age: 30
Birth Year: 1993

String representation of the object:
Alice, 30 years old

Explicit string conversion:
Alice, 30 years old

Comment fonctionne __str__

Dans le code ci-dessus :

  • Nous avons défini une méthode __str__ qui renvoie une chaîne formatée avec le nom et l'âge de la personne.
  • Lorsque nous appelons print(person1), Python appelle automatiquement la méthode __str__ pour déterminer ce qu'il faut afficher.
  • Nous pouvons également convertir explicitement un objet en chaîne de caractères en utilisant str(person1), ce qui appelle également la méthode __str__.

Que se passe-t-il sans __str__

Pour comprendre l'importance de la méthode __str__, voyons ce qui se passe lorsque nous ne la définissons pas :

  1. Créez un nouveau fichier appelé without_str.py :

  2. Ajoutez le code suivant :

    class SimpleClass:
        def __init__(self, value):
            self.value = value
    
    ## Create an object
    obj = SimpleClass(42)
    
    ## Print the object
    print(obj)
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 without_str.py

Vous devriez voir une sortie comme :

<__main__.SimpleClass object at 0x7f2d8c3e9d90>

La sortie n'est pas très informative. Elle affiche le nom de la classe et l'adresse mémoire de l'objet, mais pas son contenu. C'est pourquoi l'implémentation d'une méthode __str__ appropriée est importante pour rendre vos objets plus conviviaux.

Exercice pratique

Créons une nouvelle classe appelée Book avec une méthode __str__ :

  1. Créez un nouveau fichier appelé book.py :

  2. Ajoutez le code suivant :

    class Book:
        def __init__(self, title, author, pages):
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return f'"{self.title}" by {self.author} ({self.pages} pages)'
    
    ## Create book objects
    book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
    book2 = Book("To Kill a Mockingbird", "Harper Lee", 281)
    
    ## Print the books
    print(book1)
    print(book2)
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 book.py

La sortie devrait être :

"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)

Vous comprenez maintenant comment utiliser la méthode __str__ pour créer des représentations sous forme de chaîne de caractères lisibles par l'homme de vos objets.

Implémentation de la méthode __repr__

En plus de __str__, Python fournit une autre méthode spéciale pour la représentation sous forme de chaîne de caractères : __repr__. Alors que __str__ est destiné à fournir une représentation lisible par l'homme, __repr__ est destiné à fournir une représentation non ambiguë d'un objet qui peut être utilisée pour recréer l'objet si possible.

Comprendre la méthode __repr__

La méthode __repr__ est appelée lorsque vous utilisez la fonction repr() sur un objet ou lorsque vous affichez un objet dans une session interactive. Elle doit renvoyer une chaîne de caractères qui, lorsqu'elle est passée à eval(), créerait un objet équivalent (si possible).

Mettons à jour notre classe Book pour inclure une méthode __repr__ :

  1. Ouvrez book.py dans l'éditeur.

  2. Mettez à jour le code pour inclure une méthode __repr__ :

    class Book:
        def __init__(self, title, author, pages):
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return f'"{self.title}" by {self.author} ({self.pages} pages)'
    
        def __repr__(self):
            return f'Book("{self.title}", "{self.author}", {self.pages})'
    
    ## Create book objects
    book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
    book2 = Book("To Kill a Mockingbird", "Harper Lee", 281)
    
    ## Print the books (uses __str__)
    print("String representation (using __str__):")
    print(book1)
    print(book2)
    
    ## Get the representation (uses __repr__)
    print("\nRepresentation (using __repr__):")
    print(repr(book1))
    print(repr(book2))
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 book.py

Vous devriez voir une sortie comme :

String representation (using __str__):
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)

Representation (using __repr__):
Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
Book("To Kill a Mockingbird", "Harper Lee", 281)

Différences entre __str__ et __repr__

Les principales différences entre __str__ et __repr__ sont :

  • __str__ est destiné à une sortie lisible par l'homme, tandis que __repr__ est destiné aux développeurs et au débogage.
  • Si __str__ n'est pas défini mais que __repr__ l'est, Python utilisera __repr__ comme solution de repli pour str() ou print().
  • __repr__ doit idéalement renvoyer une chaîne de caractères qui peut recréer l'objet lorsqu'elle est passée à eval(), bien que cela ne soit pas toujours possible ou nécessaire.

La fonction eval() avec __repr__

Lorsqu'elle est implémentée correctement, la chaîne de caractères renvoyée par __repr__ peut être utilisée avec eval() pour recréer l'objet. Voyons cela en action :

  1. Créez un nouveau fichier appelé repr_eval.py :

  2. Ajoutez le code suivant :

    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __str__(self):
            return f"Point at ({self.x}, {self.y})"
    
        def __repr__(self):
            return f"Point({self.x}, {self.y})"
    
    ## Create a point
    p1 = Point(3, 4)
    
    ## Get the repr string
    repr_str = repr(p1)
    print(f"Representation: {repr_str}")
    
    ## Use eval to recreate the object
    p2 = eval(repr_str)
    print(f"Recreated object: {p2}")
    
    ## Verify they have the same values
    print(f"p1.x = {p1.x}, p1.y = {p1.y}")
    print(f"p2.x = {p2.x}, p2.y = {p2.y}")
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 repr_eval.py

Vous devriez voir une sortie comme :

Representation: Point(3, 4)
Recreated object: Point at (3, 4)
p1.x = 3, p1.y = 4
p2.x = 3, p2.y = 4

Cela démontre que nous pouvons recréer l'objet d'origine en utilisant la chaîne de caractères renvoyée par __repr__ et la fonction eval().

Quand utiliser chaque méthode

  • Utilisez __init__ pour configurer l'état initial de vos objets.
  • Utilisez __str__ pour fournir une représentation lisible par l'homme pour les utilisateurs finaux.
  • Utilisez __repr__ pour fournir une représentation précise et non ambiguë pour les développeurs et le débogage.

Exercice pratique : Un exemple complet

Regroupons le tout en créant une classe Rectangle avec les trois méthodes spéciales :

  1. Créez un nouveau fichier appelé rectangle.py :

  2. Ajoutez le code suivant :

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            return self.width * self.height
    
        def perimeter(self):
            return 2 * (self.width + self.height)
    
        def __str__(self):
            return f"Rectangle with width {self.width} and height {self.height}"
    
        def __repr__(self):
            return f"Rectangle({self.width}, {self.height})"
    
    ## Create rectangles
    rect1 = Rectangle(5, 10)
    rect2 = Rectangle(3, 7)
    
    ## Display information about the rectangles
    print(f"Rectangle 1: {rect1}")
    print(f"Area: {rect1.area()}")
    print(f"Perimeter: {rect1.perimeter()}")
    print(f"Representation: {repr(rect1)}")
    
    print("\nRectangle 2: {0}".format(rect2))
    print(f"Area: {rect2.area()}")
    print(f"Perimeter: {rect2.perimeter()}")
    print(f"Representation: {repr(rect2)}")
    
    ## Recreate a rectangle using eval
    rect3 = eval(repr(rect1))
    print(f"\nRecreated rectangle: {rect3}")
    print(f"Is it the same area? {rect3.area() == rect1.area()}")
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 rectangle.py

Vous devriez voir une sortie comme :

Rectangle 1: Rectangle with width 5 and height 10
Area: 50
Perimeter: 30
Representation: Rectangle(5, 10)

Rectangle 2: Rectangle with width 3 and height 7
Area: 21
Perimeter: 20
Representation: Rectangle(3, 7)

Recreated rectangle: Rectangle with width 5 and height 10
Is it the same area? True

Cet exemple démontre comment les trois méthodes spéciales (__init__, __str__ et __repr__) fonctionnent ensemble pour créer une classe bien conçue.

Création d'une application pratique

Maintenant que nous comprenons les trois méthodes spéciales __init__, __str__ et __repr__, créons une application plus pratique qui démontre leur utilisation dans un scénario réel. Nous allons créer un système bancaire simple avec une classe BankAccount.

Construction d'une classe Bank Account

  1. Créez un nouveau fichier appelé bank_account.py :

  2. Ajoutez le code suivant :

    class BankAccount:
        def __init__(self, account_number, owner_name, balance=0.0):
            self.account_number = account_number
            self.owner_name = owner_name
            self.balance = balance
            self.transactions = []
    
        def deposit(self, amount):
            if amount <= 0:
                print("Deposit amount must be positive")
                return False
    
            self.balance += amount
            self.transactions.append(f"Deposit: +${amount:.2f}")
            return True
    
        def withdraw(self, amount):
            if amount <= 0:
                print("Withdrawal amount must be positive")
                return False
    
            if amount > self.balance:
                print("Insufficient funds")
                return False
    
            self.balance -= amount
            self.transactions.append(f"Withdrawal: -${amount:.2f}")
            return True
    
        def get_transaction_history(self):
            return self.transactions
    
        def __str__(self):
            return f"Account {self.account_number} | Owner: {self.owner_name} | Balance: ${self.balance:.2f}"
    
        def __repr__(self):
            return f'BankAccount("{self.account_number}", "{self.owner_name}", {self.balance})'
  3. Enregistrez le fichier.

Test de la classe Bank Account

Testons maintenant notre classe BankAccount :

  1. Créez un nouveau fichier appelé bank_test.py :

  2. Ajoutez le code suivant :

    from bank_account import BankAccount
    
    ## Create bank accounts
    account1 = BankAccount("12345", "John Doe", 1000.0)
    account2 = BankAccount("67890", "Jane Smith", 500.0)
    
    ## Display initial account information
    print("Initial Account Status:")
    print(account1)
    print(account2)
    
    ## Perform transactions
    print("\nPerforming transactions...")
    
    ## Deposit to account1
    print("\nDepositing $250 to account1:")
    account1.deposit(250)
    print(account1)
    
    ## Withdraw from account2
    print("\nWithdrawing $100 from account2:")
    account2.withdraw(100)
    print(account2)
    
    ## Try to withdraw too much from account2
    print("\nTrying to withdraw $1000 from account2:")
    account2.withdraw(1000)
    print(account2)
    
    ## Try to deposit a negative amount to account1
    print("\nTrying to deposit -$50 to account1:")
    account1.deposit(-50)
    print(account1)
    
    ## Display transaction history
    print("\nTransaction history for account1:")
    for transaction in account1.get_transaction_history():
        print(f"- {transaction}")
    
    print("\nTransaction history for account2:")
    for transaction in account2.get_transaction_history():
        print(f"- {transaction}")
    
    ## Recreate an account using repr
    print("\nRecreating account1 using repr:")
    account1_repr = repr(account1)
    print(f"Representation: {account1_repr}")
    
    recreated_account = eval(account1_repr)
    print(f"Recreated account: {recreated_account}")
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 bank_test.py

Vous devriez voir une sortie similaire à celle-ci :

Initial Account Status:
Account 12345 | Owner: John Doe | Balance: $1000.00
Account 67890 | Owner: Jane Smith | Balance: $500.00

Performing transactions...

Depositing $250 to account1:
Account 12345 | Owner: John Doe | Balance: $1250.00

Withdrawing $100 from account2:
Account 67890 | Owner: Jane Smith | Balance: $400.00

Trying to withdraw $1000 from account2:
Insufficient funds
Account 67890 | Owner: Jane Smith | Balance: $400.00

Trying to deposit -$50 to account1:
Deposit amount must be positive
Account 12345 | Owner: John Doe | Balance: $1250.00

Transaction history for account1:
- Deposit: +$250.00

Transaction history for account2:
- Withdrawal: -$100.00

Recreating account1 using repr:
Representation: BankAccount("12345", "John Doe", 1250.0)
Recreated account: Account 12345 | Owner: John Doe | Balance: $1250.00

Comprendre l'implémentation

Dans cette application bancaire :

  1. La méthode __init__ initialise un compte bancaire avec un numéro de compte, le nom du propriétaire et un solde initial facultatif. Elle crée également une liste vide pour suivre les transactions.

  2. Les méthodes deposit et withdraw gèrent les transactions et mettent à jour le solde et l'historique des transactions.

  3. La méthode __str__ fournit une représentation conviviale du compte, affichant le numéro de compte, le nom du propriétaire et le solde actuel.

  4. La méthode __repr__ fournit une chaîne de caractères qui peut recréer l'objet compte (bien que l'historique des transactions ne soit pas conservé dans cette implémentation).

Cet exemple démontre comment ces méthodes spéciales peuvent être utilisées dans une application pratique pour créer des objets plus intuitifs et conviviaux.

Extension de l'application

En guise d'exercice final, créons un système bancaire simple qui gère plusieurs comptes :

  1. Créez un nouveau fichier appelé banking_system.py :

  2. Ajoutez le code suivant :

    from bank_account import BankAccount
    
    class BankingSystem:
        def __init__(self, bank_name):
            self.bank_name = bank_name
            self.accounts = {}
    
        def create_account(self, account_number, owner_name, initial_balance=0.0):
            if account_number in self.accounts:
                print(f"Account {account_number} already exists")
                return None
    
            account = BankAccount(account_number, owner_name, initial_balance)
            self.accounts[account_number] = account
            return account
    
        def get_account(self, account_number):
            return self.accounts.get(account_number)
    
        def list_accounts(self):
            return list(self.accounts.values())
    
        def __str__(self):
            return f"{self.bank_name} - Managing {len(self.accounts)} accounts"
    
        def __repr__(self):
            return f'BankingSystem("{self.bank_name}")'
    
    ## Create a banking system
    bank = BankingSystem("Python First Bank")
    print(bank)
    
    ## Create some accounts
    bank.create_account("A001", "John Doe", 1000)
    bank.create_account("A002", "Jane Smith", 500)
    bank.create_account("A003", "Bob Johnson", 250)
    
    ## List all accounts
    print("\nAll accounts:")
    for account in bank.list_accounts():
        print(account)
    
    ## Make some transactions
    account = bank.get_account("A001")
    if account:
        print(f"\nBefore deposit: {account}")
        account.deposit(500)
        print(f"After deposit: {account}")
    
    account = bank.get_account("A002")
    if account:
        print(f"\nBefore withdrawal: {account}")
        account.withdraw(200)
        print(f"After withdrawal: {account}")
    
    ## Try to create an existing account
    print("\nTrying to create an existing account:")
    bank.create_account("A001", "Someone Else", 300)
    
    ## Final state of the banking system
    print(f"\n{bank}")
  3. Enregistrez le fichier.

  4. Exécutez le script :

    python3 banking_system.py

Vous devriez voir une sortie similaire à :

Python First Bank - Managing 0 accounts

All accounts:
Account A001 | Owner: John Doe | Balance: $1000.00
Account A002 | Owner: Jane Smith | Balance: $500.00
Account A003 | Owner: Bob Johnson | Balance: $250.00

Before deposit: Account A001 | Owner: John Doe | Balance: $1000.00
After deposit: Account A001 | Owner: John Doe | Balance: $1500.00

Before withdrawal: Account A002 | Owner: Jane Smith | Balance: $500.00
After withdrawal: Account A002 | Owner: Jane Smith | Balance: $300.00

Trying to create an existing account:
Account A001 already exists

Python First Bank - Managing 3 accounts

Cet exemple démontre comment utiliser la classe BankAccount que nous avons créée précédemment dans une application plus complète. Il montre comment les méthodes spéciales __init__, __str__ et __repr__ fournissent une base solide pour la création de classes intuitives et conviviales.

Résumé

Dans ce laboratoire, vous avez appris trois méthodes spéciales essentielles en Python qui vous aident à créer des objets plus expressifs et conviviaux :

  1. __init__ : La méthode constructeur qui initialise les attributs d'un objet lors de sa création.

  2. __str__ : Une méthode qui fournit une représentation sous forme de chaîne de caractères lisible par l'homme d'un objet, principalement pour les utilisateurs finaux.

  3. __repr__ : Une méthode qui fournit une représentation sous forme de chaîne de caractères non ambiguë d'un objet, principalement pour les développeurs et le débogage.

Vous avez implémenté ces méthodes dans plusieurs exemples, allant de classes simples comme Person et Book à des applications plus complexes comme un système bancaire. Vous avez également appris les différences entre __str__ et __repr__ et quand utiliser chacune d'elles.

En maîtrisant ces méthodes spéciales, vous pouvez créer des objets Python plus intuitifs et personnalisés qui s'intègrent de manière transparente aux fonctionnalités intégrées du langage. Ces connaissances constituent une partie essentielle de la programmation orientée objet en Python et vous aideront à écrire un code plus propre et plus facile à maintenir.

Au fur et à mesure que vous poursuivez votre parcours Python, rappelez-vous qu'il existe de nombreuses autres méthodes spéciales disponibles qui vous permettent de personnaliser davantage le comportement de vos objets. Explorer ces méthodes vous donnera encore plus de contrôle sur la façon dont vos objets se comportent dans différents contextes.