Python 基础语法与数据类型(十二) - 构造方法 (`__init__`) 与实例属性


创作不易,请各位看官顺手点点关注,不胜感激 。

本篇将深入剖析类中一个至关重要的特殊方法:构造方法 __init__,以及它如何帮助我们创建拥有独特数据的实例属性。理解 __init__ 是掌握面向对象编程的关键一步,因为它决定了你创建的每个对象“长什么样”以及它“自带”了哪些初始数据。

1. 什么是构造方法 (__init__)?

在 Python 中,__init__(注意是两个下划线开头和结尾)是一个特殊的方法,被称为构造方法初始化方法。它的作用是:

  1. 当一个类的新实例(对象)被创建时,__init__ 方法会自动被调用
  2. 它负责对新创建的实例进行初始化,即设置该实例特有的属性(数据)

可以把 __init__ 想象成一个对象的“出生证明”或“配置器”。每当一个新对象诞生时,它都会根据 __init__ 的指示,获取它独有的特征。

1.1 __init__ 方法的语法

__init__ 方法的定义与普通方法类似,但有一些关键点:

class ClassName:
    def __init__(self, parameter1, parameter2, ...):
        # self 是必须的第一个参数,指向正在创建的实例本身
        # self.attribute_name = parameter1 # 设置实例属性
        # self.another_attribute = value    # 可以在这里设置默认值
  • self: 这是 __init__ 方法(以及所有实例方法)的第一个参数。它是一个约定俗成的名称,指代正在被创建或操作的当前实例(对象)本身。当你通过 ClassName() 创建一个对象时,Python 会自动将这个新创建的对象作为 self 参数传递给 __init__
  • 其他参数: self 之后的参数是你在创建对象时需要传入的值,这些值将用于初始化对象的实例属性。例如,创建一个 Person 对象可能需要 nameage
1.2 __init__ 的工作原理:幕后解析

让我们通过一个 Person 类的例子来理解 __init__ 的工作原理:

class Person:
    def __init__(self, name, age):
        """
        构造方法:在创建 Person 对象时自动调用,用于初始化实例属性。
        Args:
            name (str): 人的姓名。
            age (int): 人的年龄。
        """
        print(f"正在初始化一个名为 {name},年龄为 {age} 的 Person 对象...")
        self.name = name  # 将传入的 name 值赋给当前实例的 name 属性
        self.age = age    # 将传入的 age 值赋给当前实例的 age 属性
        self.is_adult = (age >= 18) # 也可以根据参数计算并设置其他属性

    def introduce(self):
        """介绍自己。"""
        print(f"大家好,我叫 {self.name},我今年 {self.age} 岁了。")
        if self.is_adult:
            print("我是一名成年人。")
        else:
            print("我还是一名青少年。")

# 创建 Person 类的实例
print("--- 准备创建 person1 ---")
person1 = Person("Alice", 30) # ① Python 自动调用 __init__,将 person1 作为 self,"Alice" 作为 name,30 作为 age
print("--- person1 创建完成 ---")
person1.introduce()

print("\n--- 准备创建 person2 ---")
person2 = Person("Bob", 15)   # ② 再次调用 __init__,将 person2 作为 self,"Bob" 作为 name,15 作为 age
print("--- person2 创建完成 ---")
person2.introduce()

输出:

--- 准备创建 person1 ---
正在初始化一个名为 Alice,年龄为 30 的 Person 对象...
--- person1 创建完成 ---
大家好,我叫 Alice,我今年 30 岁了。
我是一名成年人。

--- 准备创建 person2 ---
正在初始化一个名为 Bob,年龄为 15 的 Person 对象...
--- person2 创建完成 ---
大家好,我叫 Bob,我今年 15 岁了。
我还是一名青少年。

工作流程解析:

  1. 当你执行 person1 = Person("Alice", 30) 时:
    • Python 首先在内存中为新的 Person 对象分配空间
    • 然后,它自动调用 Person 类中定义的 __init__ 方法。
    • 此时,__init__ 方法内部的 self 参数会自动指向刚刚创建的那个新对象。name 接收 "Alice"age 接收 30
    • self.name = name 这一行代码的含义是:将传入的 name (即 "Alice") 赋值给当前对象 (self) 的一个名为 name 的属性。同理,self.age = age
    • __init__ 方法执行完毕后,这个已经被初始化了属性的新对象被赋值给了变量 person1
  2. person1person2 是两个独立的实例,它们各自拥有自己的 nameageis_adult 属性,互不影响。每次调用 Person(...) 都会触发 __init__ 的执行,从而创建一个全新的、独立的实例。

2. 实例属性 (Instance Attributes)

通过 __init__ 方法,我们为每个对象设置了它独有的数据,这些数据就是实例属性。实例属性是每个对象特有的,它们的值可以不同。

例如,在 Person 类中:

  • self.name 是一个实例属性。person1name 是 “Alice”,person2name 是 “Bob”。
  • self.age 是一个实例属性。person1age 是 30,person2age 是 15。
  • self.is_adult 也是一个实例属性,它的值根据 age 的不同而不同。
2.1 访问与修改实例属性

你可以随时使用点 . 运算符来访问或修改对象的实例属性:

# 访问实例属性
print(f"person1 的姓名: {person1.name}")
print(f"person2 的年龄: {person2.age}")

# 修改实例属性
person1.age = 31 # Alice 又过了一岁
print(f"person1 现在 {person1.age} 岁了。")

# 添加新的实例属性 (不推荐在 __init__ 之外随意添加,因为其他实例没有该属性)
person1.city = "New York"
print(f"person1 居住在: {person1.city}")

# print(person2.city) # 这会报错,因为 person2 没有 city 属性

输出:

person1 的姓名: Alice
person2 的年龄: 15
person1 现在 31 岁了。
person1 居住在: New York
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'Person' object has no attribute 'city'

重要提示: 虽然 Python 允许你在 __init__ 之外动态地为对象添加新属性,但强烈建议在 __init__ 中定义所有核心实例属性。这使得类的结构更清晰,每个对象创建后都有预期的属性集,便于代码维护和理解。在 __init__ 之外添加属性可能会导致其他实例没有该属性,从而引发 AttributeError

2.2 实例属性与类属性回顾

再次强调 __init__ 中定义的实例属性与定义在类体中的类属性的区别:

特性实例属性 (在 __init__ 中用 self.)类属性 (在类体中)
归属属于每个独立的对象(实例)属于类本身,所有实例共享
定义通常在 __init__ 方法中通过 self.name = value 定义在类体中,所有方法之外定义 name = value
特点每个实例都有自己的一份,值可以不同所有实例共享同一份,值相同
访问只能通过 instance_name.attribute_name 访问通常通过 ClassName.attribute_name 访问,也可以通过 instance_name.attribute_name 访问
用途存储对象特有的数据存储类层面的常量或所有实例共有的默认值
修改修改只影响当前实例修改会影响所有实例(通过类名修改时)

3. __init__ 与默认参数

__init__ 方法也可以使用我们之前学过的默认参数。这使得在创建对象时,某些属性可以有预设值,从而使实例化更加灵活。

class Book:
    def __init__(self, title, author="佚名", pages=100): # author 和 pages 有默认值
        self.title = title
        self.author = author
        self.pages = pages
        self.is_read = False # 额外设置一个默认状态属性

    def display_info(self):
        read_status = "已读" if self.is_read else "未读"
        print(f"《{self.title}》 - {self.author}, 共 {self.pages} 页, 状态: {read_status}")

# 创建实例时使用默认值
book1 = Book("Python 快速入门")
book1.display_info()

# 创建实例时覆盖默认值
book2 = Book("Effective Python", "Brett Slatkin", 300)
book2.display_info()

# 覆盖部分默认值 (使用关键字参数更清晰)
book3 = Book("算法导论", pages=1200) # 仅指定 title 和 pages,author 使用默认值
book3.display_info()

# 修改属性
book1.is_read = True
book1.display_info()

输出:

《Python 快速入门》 - 佚名, 共 100 页, 状态: 未读
《Effective Python》 - Brett Slatkin, 共 300 页, 状态: 未读
《算法导论》 - 佚名, 共 1200 页, 状态: 未读
《Python 快速入门》 - 佚名, 共 100 页, 状态: 已读

⚠️ 再次提醒: 如果你的 __init__ 方法中的默认参数是可变类型(如列表 [] 或字典 {}),请务必使用 None 作为默认值并在方法内部初始化,以避免所有实例共享同一个可变对象带来的陷阱。

class Student:
    def __init__(self, name, courses=None): # 正确处理可变默认参数
        self.name = name
        self.courses = [] if courses is None else list(courses) # 避免共享可变对象

    def add_course(self, course_name):
        self.courses.append(course_name)

# 正确的用法
student1 = Student("Alice")
student1.add_course("Math")
print(f"{student1.name}'s courses: {student1.courses}") # ['Math']

student2 = Student("Bob") # 创建新实例时,会创建新的空列表
student2.add_course("Physics")
print(f"{student2.name}'s courses: {student2.courses}") # ['Physics']

print(f"Original Alice's courses: {student1.courses}") # ['Math'],Alice 的课程没有受到影响

总结

__init__ 方法是 Python 中类的核心,它定义了对象在创建之初所拥有的初始状态和数据。通过 __init__,你可以:

  • 初始化实例属性:为每个对象设置其独有的数据。
  • 确保数据完整性:在对象“诞生”时就具备所有必要的属性。
  • 提高代码可读性:一眼就能看出创建该对象需要哪些参数以及它会拥有哪些初始特征。
  • self__init__ 和所有实例方法的灵魂,它使得方法能够操作和访问当前实例的数据。

理解 __init__ 和实例属性,是深入理解面向对象编程的基石。

练习题

尝试独立完成以下练习题,并通过答案进行对照:

  1. 基础 Dog 类:

    • 定义一个 Dog 类。
    • __init__ 方法中,接收 namebreed(品种)作为参数,并将它们保存为实例属性。
    • 定义一个方法 bark(),打印 "汪!我的名字是 [name]!"
    • 创建两只狗的对象,一只叫 “Buddy”,品种 “Golden Retriever”;另一只叫 “Lucy”,品种 “Labrador”。
    • 分别让它们叫一声。
  2. Product 类与默认参数:

    • 定义一个 Product 类。
    • __init__ 方法中,接收 name(产品名称), price(价格), quantity(数量,默认值为 1)作为参数,并保存为实例属性。
    • 定义一个方法 get_total_cost(),返回产品的总价格(价格 * 数量)。
    • 定义一个方法 display_product_info(),打印产品名称、价格、数量和总价格。
    • 创建三个产品对象:
      • “Laptop”,价格 1200,数量 1。
      • “Mouse”,价格 25,数量 5。
      • “Keyboard”,价格 75(使用默认数量)。
    • 分别显示这些产品的信息。
  3. BankAccount 类与状态管理:

    • 定义一个 BankAccount 类。
    • __init__ 方法中,接收 account_holder(持有人姓名)和 initial_balance(初始余额,默认值为 0)作为参数。
    • 保存 account_holderbalance 为实例属性。
    • 定义一个方法 deposit(amount),用于存款。确保 amount 大于 0。
    • 定义一个方法 withdraw(amount),用于取款。确保 amount 大于 0 且不超过当前余额。
    • 定义一个方法 get_balance(),返回当前余额。
    • 创建两个银行账户对象,并进行存款、取款操作,然后打印余额。
  4. ShoppingCart 类(使用可变默认参数的正确方式):

    • 定义一个 ShoppingCart 类。
    • __init__ 方法中,接收 customer_name 作为参数,并初始化一个空的 items 列表作为实例属性。注意,这里 items 列表是实例特有的,不要用可变默认参数的陷阱方式。
    • 定义一个方法 add_item(item_name, price, quantity=1),将商品添加到 items 列表中。每个商品可以存储为一个字典 {'name': item_name, 'price': price, 'quantity': quantity}
    • 定义一个方法 get_total_items(),返回购物车中商品的总件数(所有商品的 quantity 之和)。
    • 定义一个方法 get_total_cost(),返回购物车中所有商品的总价。
    • 创建两个购物车对象,分别添加不同的商品,然后打印它们各自的总件数和总价。

练习题答案

1. 基础 Dog 类:

# 1. 基础 Dog 类
class Dog:
    def __init__(self, name, breed):
        """
        初始化 Dog 对象。
        Args:
            name (str): 狗的名字。
            breed (str): 狗的品种。
        """
        self.name = name
        self.breed = breed

    def bark(self):
        """狗叫一声,并报上自己的名字。"""
        print(f"汪!我的名字是 {self.name}!")

# 创建 Dog 对象
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Lucy", "Labrador")

# 让它们叫
dog1.bark()
dog2.bark()

# 访问属性
print(f"{dog1.name} 的品种是 {dog1.breed}")
print(f"{dog2.name} 的品种是 {dog2.breed}")

输出:

汪!我的名字是 Buddy!
汪!我的名字是 Lucy!
Buddy 的品种是 Golden Retriever
Lucy 的品种是 Labrador

2. Product 类与默认参数:

# 2. Product 类与默认参数
class Product:
    def __init__(self, name, price, quantity=1):
        """
        初始化 Product 对象。
        Args:
            name (str): 产品名称。
            price (float/int): 产品价格。
            quantity (int, optional): 产品数量。默认为 1。
        """
        self.name = name
        self.price = price
        self.quantity = quantity

    def get_total_cost(self):
        """返回产品的总价格。"""
        return self.price * self.quantity

    def display_product_info(self):
        """打印产品信息。"""
        total_cost = self.get_total_cost()
        print(f"--- 产品信息 ---")
        print(f"名称: {self.name}")
        print(f"价格: ${self.price:.2f}")
        print(f"数量: {self.quantity}")
        print(f"总价: ${total_cost:.2f}")
        print("-" * 15)

# 创建产品对象
product1 = Product("Laptop", 1200) # 数量使用默认值 1
product2 = Product("Mouse", 25, 5)
product3 = Product("Keyboard", 75) # 数量使用默认值 1

# 显示产品信息
product1.display_product_info()
product2.display_product_info()
product3.display_product_info()

输出:

--- 产品信息 ---
名称: Laptop
价格: $1200.00
数量: 1
总价: $1200.00
---------------
--- 产品信息 ---
名称: Mouse
价格: $25.00
数量: 5
总价: $125.00
---------------
--- 产品信息 ---
名称: Keyboard
价格: $75.00
数量: 1
总价: $75.00
---------------

3. BankAccount 类与状态管理:

# 3. BankAccount 类与状态管理
class BankAccount:
    def __init__(self, account_holder, initial_balance=0):
        """
        初始化 BankAccount 对象。
        Args:
            account_holder (str): 账户持有人姓名。
            initial_balance (float, optional): 初始余额。默认为 0。
        """
        self.account_holder = account_holder
        # 确保初始余额不为负
        self.balance = max(0, float(initial_balance))
        print(f"账户 '{self.account_holder}' 已创建,初始余额: ${self.balance:.2f}")

    def deposit(self, amount):
        """
        存款操作。
        Args:
            amount (float): 存款金额。
        """
        if amount > 0:
            self.balance += amount
            print(f"存入 ${amount:.2f}。当前余额: ${self.balance:.2f}")
        else:
            print("存款金额必须大于零。")

    def withdraw(self, amount):
        """
        取款操作。
        Args:
            amount (float): 取款金额。
        """
        if amount <= 0:
            print("取款金额必须大于零。")
        elif amount > self.balance:
            print(f"余额不足。当前余额: ${self.balance:.2f},尝试取款: ${amount:.2f}")
        else:
            self.balance -= amount
            print(f"取出 ${amount:.2f}。当前余额: ${self.balance:.2f}")

    def get_balance(self):
        """返回当前余额。"""
        return self.balance

# 创建银行账户对象
account1 = BankAccount("Alice Smith", 1000)
account2 = BankAccount("Bob Johnson") # 使用默认初始余额

print(f"\n{account1.account_holder} 账户操作:")
account1.deposit(200)
account1.withdraw(500)
account1.withdraw(1000) # 尝试超额取款
print(f"Alice 账户最终余额: ${account1.get_balance():.2f}")

print(f"\n{account2.account_holder} 账户操作:")
account2.deposit(50)
account2.deposit(100)
account2.withdraw(75)
print(f"Bob 账户最终余额: ${account2.get_balance():.2f}")

输出:

账户 'Alice Smith' 已创建,初始余额: $1000.00
账户 'Bob Johnson' 已创建,初始余额: $0.00

Alice Smith 账户操作:
存入 $200.00。当前余额: $1200.00
取出 $500.00。当前余额: $700.00
余额不足。当前余额: $700.00,尝试取款: $1000.00
Alice 账户最终余额: $700.00

Bob Johnson 账户操作:
存入 $50.00。当前余额: $50.00
存入 $100.00。当前余额: $150.00
取出 $75.00。当前余额: $75.00
Bob 账户最终余额: $75.00

4. ShoppingCart 类(使用可变默认参数的正确方式):

# 4. ShoppingCart 类 (使用可变默认参数的正确方式)
class ShoppingCart:
    def __init__(self, customer_name):
        """
        初始化 ShoppingCart 对象。
        Args:
            customer_name (str): 顾客姓名。
        """
        self.customer_name = customer_name
        self.items = [] # 每个购物车实例都有自己独立的空列表

    def add_item(self, item_name, price, quantity=1):
        """
        向购物车添加商品。
        Args:
            item_name (str): 商品名称。
            price (float): 商品单价。
            quantity (int, optional): 商品数量。默认为 1。
        """
        if price > 0 and quantity > 0:
            self.items.append({'name': item_name, 'price': price, 'quantity': quantity})
            print(f"已将 {quantity} 个 '{item_name}' 添加到 {self.customer_name} 的购物车。")
        else:
            print(f"无法添加 '{item_name}': 价格和数量必须大于零。")

    def get_total_items(self):
        """返回购物车中商品的总件数。"""
        total_quantity = 0
        for item in self.items:
            total_quantity += item['quantity']
        return total_quantity

    def get_total_cost(self):
        """返回购物车中所有商品的总价。"""
        total_price = 0
        for item in self.items:
            total_price += item['price'] * item['quantity']
        return total_price

    def display_cart_summary(self):
        """打印购物车摘要。"""
        print(f"\n--- {self.customer_name} 的购物车摘要 ---")
        if not self.items:
            print("购物车为空。")
        else:
            for item in self.items:
                print(f"- {item['name']} (单价: ${item['price']:.2f}, 数量: {item['quantity']})")
            print(f"总件数: {self.get_total_items()}")
            print(f"总价格: ${self.get_total_cost():.2f}")
        print("-" * 30)

# 创建购物车对象
cart1 = ShoppingCart("Sarah")
cart2 = ShoppingCart("John")

# 为 Sarah 的购物车添加商品
cart1.add_item("Laptop", 1200)
cart1.add_item("Mouse", 25, 2)
cart1.add_item("Keyboard", 75)

# 为 John 的购物车添加商品
cart2.add_item("Headphones", 150)
cart2.add_item("USB Drive", 10, 3)

# 显示购物车摘要
cart1.display_cart_summary()
cart2.display_cart_summary()

# 验证两个购物车是独立的
print(f"\nSarah 的购物车商品列表: {cart1.items}")
print(f"John 的购物车商品列表: {cart2.items}")

输出:

已将 1 个 'Laptop' 添加到 Sarah 的购物车。
已将 2 个 'Mouse' 添加到 Sarah 的购物车。
已将 1 个 'Keyboard' 添加到 Sarah 的购物车。
已将 1 个 'Headphones' 添加到 John 的购物车。
已将 3 个 'USB Drive' 添加到 John 的购物车。

--- Sarah 的购物车摘要 ---
- Laptop (单价: $1200.00, 数量: 1)
- Mouse (单价: $25.00, 数量: 2)
- Keyboard (单价: $75.00, 数量: 1)
总件数: 4
总价格: $1325.00
------------------------------

--- John 的购物车摘要 ---
- Headphones (单价: $150.00, 数量: 1)
- USB Drive (单价: $10.00, 数量: 3)
总件数: 4
总价格: $180.00
------------------------------

Sarah 的购物车商品列表: [{'name': 'Laptop', 'price': 1200, 'quantity': 1}, {'name': 'Mouse', 'price': 25, 'quantity': 2}, {'name': 'Keyboard', 'price': 75, 'quantity': 1}]
John 的购物车商品列表: [{'name': 'Headphones', 'price': 150, 'quantity': 1}, {'name': 'USB Drive', 'price': 10, 'quantity': 3}]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值