Python 中 __init__、__str__ 和 __repr__ 方法的使用方法

PythonBeginner
立即练习

介绍

Python 的面向对象编程(OOP)特性提供了强大的工具,用于创建富有表现力和定制化的对象。在这些特性中,特殊方法 __init____str____repr__ 在定义 Python 对象的行为和表示方面起着至关重要的作用。

在本教程中,我们将探讨这些特殊方法,了解它们的目的,并学习如何在你的 Python 类中有效地实现它们。在完成这个实验后,你将能够创建更直观、用户友好的 Python 对象。

使用 __init__ 方法创建类

我们将要探索的第一个特殊方法是 __init__,它在创建对象时被调用。这个方法允许你初始化对象的属性。

理解 __init__ 方法

__init__ 方法,也被称为构造函数(constructor),在你创建一个类的新实例时会被自动调用。它的主要目的是通过为属性赋值来设置对象的初始状态。

让我们从创建一个带有 __init__ 方法的简单 Python 类开始:

  1. 在你的 LabEx 环境中打开终端。

  2. 导航到项目目录:

    cd ~/project
    
  3. 使用代码编辑器创建一个名为 person.py 的新 Python 文件:

  4. 在编辑器中,将以下代码添加到 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. 保存文件。

  6. 运行 Python 脚本:

    python3 person.py
    

你应该看到以下输出:

Name: Alice
Age: 30

__init__ 方法的解释

在上面的代码中:

  • 我们定义了一个 Person 类,它有一个 __init__ 方法,该方法接受三个参数:selfnameage
  • self 参数指的是正在创建的实例,并且在创建对象时会自动传递。
  • __init__ 方法内部,我们使用 self.nameself.agenameage 的值分配给对象的属性。
  • 当我们使用 person1 = Person("Alice", 30) 创建一个新的 Person 对象时,__init__ 方法会自动调用。
  • 然后,我们可以使用点符号访问属性:person1.nameperson1.age

添加更多功能

让我们通过添加一个方法来根据当前年份和人的年龄来计算出生年份,从而增强我们的 Person 类:

  1. 在编辑器中打开 person.py

  2. 更新代码以包含一个用于计算出生年份的方法:

    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. 保存文件。

  4. 运行 Python 脚本:

    python3 person.py
    

输出现在应该包括计算出的出生年份:

Name: Alice
Age: 30
Birth Year: 1993

(实际的出生年份将取决于你运行脚本时的当前年份)

现在,你已经创建了一个 Python 类,该类具有 __init__ 方法来初始化对象属性,并添加了一个方法来根据这些属性执行计算。

实现 __str__ 方法

现在我们了解了如何使用 __init__ 方法创建一个类,让我们探索另一个特殊方法,名为 __str__。这个方法允许我们定义一个对象应该如何表示为字符串。

理解 __str__ 方法

当你对一个对象使用 str() 函数,或者使用 print() 函数打印一个对象时,就会调用 __str__ 方法。它应该返回一个对象的可读字符串表示形式。

让我们更新我们的 Person 类以包含一个 __str__ 方法:

  1. 在编辑器中打开 person.py

  2. 更新代码以包含一个 __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. 保存文件。

  4. 运行 Python 脚本:

    python3 person.py
    

你应该看到类似于以下的输出:

Name: Alice
Age: 30
Birth Year: 1993

String representation of the object:
Alice, 30 years old

Explicit string conversion:
Alice, 30 years old

__str__ 的工作原理

在上面的代码中:

  • 我们定义了一个 __str__ 方法,它返回一个格式化的字符串,其中包含人的姓名和年龄。
  • 当我们调用 print(person1) 时,Python 会自动调用 __str__ 方法来确定要显示的内容。
  • 我们也可以使用 str(person1) 显式地将一个对象转换为字符串,这也会调用 __str__ 方法。

没有 __str__ 会发生什么

为了理解 __str__ 方法的重要性,让我们看看在没有定义它时会发生什么:

  1. 创建一个名为 without_str.py 的新文件:

  2. 添加以下代码:

    class SimpleClass:
        def __init__(self, value):
            self.value = value
    
    ## Create an object
    obj = SimpleClass(42)
    
    ## Print the object
    print(obj)
    
  3. 保存文件。

  4. 运行脚本:

    python3 without_str.py
    

你应该看到类似这样的输出:

<__main__.SimpleClass object at 0x7f2d8c3e9d90>

输出的信息不是很有用。它显示了类名和对象的内存地址,而不是它的内容。这就是实现一个合适的 __str__ 方法对于使你的对象更用户友好的原因。

实践练习

让我们创建一个名为 Book 的新类,并带有一个 __str__ 方法:

  1. 创建一个名为 book.py 的新文件:

  2. 添加以下代码:

    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. 保存文件。

  4. 运行脚本:

    python3 book.py
    

输出应该是:

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

现在你了解了如何使用 __str__ 方法来创建对象的可读字符串表示形式。

实现 __repr__ 方法

除了 __str__ 之外,Python 还提供了另一个用于字符串表示的特殊方法:__repr__。虽然 __str__ 旨在提供人类可读的表示形式,但 __repr__ 旨在提供对象的不二义性表示形式,如果可能,可以用来重新创建对象。

理解 __repr__ 方法

当你对一个对象使用 repr() 函数,或者在交互式会话中显示一个对象时,就会调用 __repr__ 方法。它应该返回一个字符串,当传递给 eval() 时,会创建一个等效的对象(如果可能)。

让我们更新我们的 Book 类以包含一个 __repr__ 方法:

  1. 在编辑器中打开 book.py

  2. 更新代码以包含一个 __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. 保存文件。

  4. 运行脚本:

    python3 book.py
    

你应该看到类似这样的输出:

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)

__str____repr__ 之间的区别

__str____repr__ 之间的主要区别在于:

  • __str__ 用于人类可读的输出,而 __repr__ 用于开发人员和调试。
  • 如果未定义 __str__ 但定义了 __repr__,Python 将使用 __repr__ 作为 str()print() 的后备。
  • __repr__ 理想情况下应该返回一个字符串,当传递给 eval() 时,可以重新创建对象,尽管这并非总是可能或必要的。

eval() 函数与 __repr__

当正确实现时,__repr__ 返回的字符串可以与 eval() 一起使用来重新创建对象。让我们看看实际操作:

  1. 创建一个名为 repr_eval.py 的新文件:

  2. 添加以下代码:

    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. 保存文件。

  4. 运行脚本:

    python3 repr_eval.py
    

你应该看到类似这样的输出:

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

这表明我们可以使用 __repr__ 返回的字符串和 eval() 函数来重新创建原始对象。

何时使用每种方法

  • 使用 __init__ 来设置对象的初始状态。
  • 使用 __str__ 为最终用户提供人类可读的表示形式。
  • 使用 __repr__ 为开发人员和调试提供精确、不二义性的表示形式。

实践练习:一个完整的例子

让我们将所有内容放在一起,创建一个带有所有三个特殊方法的 Rectangle 类:

  1. 创建一个名为 rectangle.py 的新文件:

  2. 添加以下代码:

    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. 保存文件。

  4. 运行脚本:

    python3 rectangle.py
    

你应该看到类似这样的输出:

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

这个例子演示了所有三个特殊方法(__init____str____repr__)如何协同工作以创建一个设计良好的类。

创建一个实用程序

现在我们了解了三个特殊方法 __init____str____repr__,让我们创建一个更实用的应用程序,它演示了它们在现实世界中的使用。我们将创建一个简单的银行系统,其中包含一个 BankAccount 类。

构建一个银行账户类

  1. 创建一个名为 bank_account.py 的新文件:

  2. 添加以下代码:

    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. 保存文件。

测试银行账户类

现在让我们测试我们的 BankAccount 类:

  1. 创建一个名为 bank_test.py 的新文件:

  2. 添加以下代码:

    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. 保存文件。

  4. 运行脚本:

    python3 bank_test.py
    

你应该看到类似这样的输出:

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

理解实现

在这个银行应用程序中:

  1. __init__ 方法使用账号、所有者姓名和一个可选的初始余额来初始化一个银行账户。它还创建一个空列表来跟踪交易。

  2. depositwithdraw 方法处理交易并更新余额和交易历史。

  3. __str__ 方法提供了账户的用户友好表示形式,显示了账号、所有者姓名和当前余额。

  4. __repr__ 方法提供了一个可以重新创建账户对象的字符串(尽管请注意,在此实现中未保留交易历史)。

这个例子演示了如何在实际应用程序中使用这些特殊方法来创建更直观和用户友好的对象。

扩展应用程序

作为最后的练习,让我们创建一个简单的银行系统来管理多个账户:

  1. 创建一个名为 banking_system.py 的新文件:

  2. 添加以下代码:

    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. 保存文件。

  4. 运行脚本:

    python3 banking_system.py
    

你应该看到类似这样的输出:

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

这个例子演示了如何在我们之前创建的 BankAccount 类中,在一个更完整的应用程序中使用。它展示了特殊方法 __init____str____repr__ 如何为创建直观且用户友好的类奠定了坚实的基础。

总结

在这个实验中,你学习了 Python 中三个重要的特殊方法,它们可以帮助你创建更具表现力和用户友好的对象:

  1. __init__:构造方法,在创建对象时初始化对象的属性。

  2. __str__:一个方法,它提供对象的人类可读的字符串表示形式,主要供最终用户使用。

  3. __repr__:一个方法,它提供对象的不二义性字符串表示形式,主要供开发人员和调试使用。

你将这些方法应用于几个示例中,从简单的类(如 PersonBook)到更复杂的应用程序(如银行系统)。你还学习了 __str____repr__ 之间的区别,以及何时使用它们。

通过掌握这些特殊方法,你可以创建更直观和自定义的 Python 对象,这些对象可以与该语言的内置功能无缝集成。这些知识构成了 Python 面向对象编程的重要组成部分,并将帮助你编写更简洁、更易于维护的代码。

在你继续你的 Python 之旅时,请记住还有许多其他特殊方法可用,它们允许你进一步自定义对象的行为。探索这些方法将让你更好地控制你的对象在不同上下文中的行为方式。