Python 基础语法与数据类型(十一) - 类 (class) 与对象 (实例)

发布于:2025-07-15 ⋅ 阅读:(21) ⋅ 点赞:(0)


前几篇文章我们学习了变量、数据类型、控制流、函数、模块和包。这些都是编写程序的基础,但当程序规模越来越大、逻辑越来越复杂时,我们就需要一种更强大的方式来组织代码和数据。这就是 面向对象编程 (Object-Oriented Programming, OOP) 的核心—— 类 (Class)对象 (Object)

面向对象编程是一种编程范式,它将数据(属性)和操作数据的方法(行为)封装在一起,形成一个独立的单元。这使得代码更具模块化、可重用性高,并且易于维护。

1. 什么是类 (Class)?

在现实世界中,我们有各种各样的“事物”:汽车、人、动物、书籍等等。它们都有一些共同的特征(比如颜色、大小)和一些能做的事情(比如移动、说话)。

在 Python 中,就是这样一种“事物”的蓝图 (Blueprint)模板 (Template)。它定义了某一类事物的共同属性(数据)和行为(函数)。类本身并不是真实存在的“事物”,它只是一个抽象的定义。

例如,我们可以定义一个 Car 类,它描述了所有汽车共同的特征(如品牌、型号、颜色)和行为(如启动、停止、加速)。

1.1 定义一个类

在 Python 中,使用 class 关键字来定义一个类。

class Car: # 类名通常使用驼峰命名法 (CamelCase),首字母大写
    """
    这是一个 Car 类,用于表示汽车。
    它定义了汽车的属性和行为。
    """
    pass # pass 语句表示一个空的类体,什么也不做,但结构完整

# 定义一个更具体的 Car 类
class Car:
    """定义一个简单的汽车类"""

    # 类属性 (Class Attributes): 属于类本身,所有实例共享的属性
    # 例如,所有 Car 对象都会有 4 个轮子
    wheels = 4

    # 实例方法 (Instance Methods): 属于对象的方法
    # __init__ 是一个特殊方法,称为构造器或初始化方法
    # 当创建一个 Car 类的对象(实例)时,这个方法会被自动调用
    # self 参数是必须的,它指向当前正在创建的对象本身
    def __init__(self, brand, model, color):
        """
        初始化 Car 对象。
        Args:
            brand (str): 汽车品牌。
            model (str): 汽车型号。
            color (str): 汽车颜色。
        """
        # 实例属性 (Instance Attributes): 属于每个对象(实例)特有的属性
        # 每个 Car 对象都会有自己的 brand, model, color
        self.brand = brand
        self.model = model
        self.color = color
        self.is_running = False # 初始状态为未启动

    def start(self):
        """启动汽车。"""
        if not self.is_running:
            self.is_running = True
            print(f"{self.brand} {self.model} 已启动。")
        else:
            print(f"{self.brand} {self.model} 已经在运行了。")

    def stop(self):
        """停止汽车。"""
        if self.is_running:
            self.is_running = False
            print(f"{self.brand} {self.model} 已停止。")
        else:
            print(f"{self.brand} {self.model} 已经停止了。")

    def display_info(self):
        """显示汽车信息。"""
        print(f"品牌: {self.brand}, 型号: {self.model}, 颜色: {self.color}, 轮子数: {Car.wheels}")

关键概念解释:

  • class Car:: 定义了一个名为 Car 的类。
  • wheels = 4: 这是一个类属性。它属于 Car 类本身,所有由 Car 类创建的对象都会共享这个 wheels 属性,并且它的值默认为 4。
  • __init__(self, brand, model, color): 这是一个特殊的方法,被称为构造器 (Constructor)初始化方法
    • 当创建一个 Car 类的实例 (Instance) 时,Python 会自动调用这个 __init__ 方法来初始化新创建的对象。
    • self: 这是一个约定俗成的参数名,必须是实例方法的第一个参数。它代表了正在被创建的对象自身。通过 self,你可以在方法内部访问和修改对象的属性。
    • brand, model, color: 这些是初始化方法接收的参数,用于设置新对象的特定属性。
  • self.brand = brand: 它们是实例属性 (Instance Attributes)。这些属性是每个对象独有的。例如,一辆奥迪车的 brand 是 “Audi”,而另一辆奔驰车的 brand 是 “Mercedes”。
  • start(self), stop(self), display_info(self): 这些是实例方法 (Instance Methods)。它们是定义在类中的函数,用于描述对象可以执行的行为。每个方法也必须将 self 作为第一个参数。

2. 什么是对象 (Object) 或实例 (Instance)?

对象 (Object)实例 (Instance) 是类的具体实现。如果说类是“汽车”这个概念的蓝图,那么对象就是一辆辆真实存在的、有具体品牌、型号和颜色的汽车,比如“我的那辆红色的特斯拉 Model 3”。

你可以从一个类创建任意数量的对象,每个对象都是独立的,拥有自己的一套实例属性值,但它们都遵循类的蓝图。

2.1 创建对象(实例化)

通过在类名后面加上圆括号 (),就像调用函数一样,就可以创建一个类的对象。这个过程称为实例化 (Instantiation)

# 创建 Car 类的对象(实例化)

# car1 是 Car 类的一个实例 (对象)
car1 = Car("Tesla", "Model 3", "Red")

# car2 是 Car 类的一个独立实例 (对象)
car2 = Car("Mercedes", "C-Class", "Black")

Car("Tesla", "Model 3", "Red") 被调用时:

  1. Python 会在内存中创建一个新的 Car 对象。
  2. Python 会自动调用 Car 类的 __init__ 方法,并将新创建的对象作为 self 参数传递,同时将 "Tesla", "Model 3", "Red" 作为 brand, model, color 参数传递进去。
  3. __init__ 方法会为 car1 这个对象设置 brand, model, coloris_running 等实例属性。

3. 访问属性和调用方法

创建对象后,你可以使用点 . 运算符来访问对象的属性和调用对象的方法。

# 访问 car1 的实例属性
print(f"Car1 品牌: {car1.brand}")    # 输出: Car1 品牌: Tesla
print(f"Car1 型号: {car1.model}")    # 输出: Car1 型号: Model 3
print(f"Car1 颜色: {car1.color}")    # 输出: Car1 颜色: Red
print(f"Car1 初始运行状态: {car1.is_running}") # 输出: Car1 初始运行状态: False

# 访问类属性 (可以通过类名或实例访问,但通过类名访问更清晰)
print(f"所有 Car 都有轮子数: {Car.wheels}") # 输出: 所有 Car 都有轮子数: 4
print(f"Car1 的轮子数: {car1.wheels}")     # 输出: Car1 的轮子数: 4

# 调用 car1 的方法
car1.start()         # 输出: Tesla Model 3 已启动。
print(f"Car1 当前运行状态: {car1.is_running}") # 输出: Car1 当前运行状态: True
car1.start()         # 输出: Tesla Model 3 已经在运行了。
car1.stop()          # 输出: Tesla Model 3 已停止。
car1.stop()          # 输出: Tesla Model 3 已经停止了。

# 调用 car1 的显示信息方法
car1.display_info()  # 输出: 品牌: Tesla, 型号: Model 3, 颜色: Red, 轮子数: 4

# 访问 car2 的属性和调用方法
print("\n--- car2 的信息 ---")
car2.display_info()  # 输出: 品牌: Mercedes, 型号: C-Class, 颜色: Black, 轮子数: 4
car2.start()         # 输出: Mercedes C-Class 已启动。

可以看到,car1car2 是两个完全独立的对象,它们各自拥有自己的 brand, model, coloris_running 属性,并且可以独立地执行 start(), stop() 等方法。但它们共享 wheels 这个类属性。

4. 类属性 vs 实例属性

再次强调一下它们的区别:

  • 类属性 (Class Attributes):

    • 定义在类体中,但在任何方法之外。
    • 属于类本身,所有该类的实例共享同一个类属性。
    • 通常用于存储与类相关但不随实例变化的值,例如常量或者所有实例通用的配置。
    • 可以通过 ClassName.attribute_nameinstance_name.attribute_name 访问(尽管通过实例访问时,Python 最终还是会向上查找类属性)。
    • 修改类属性时,会影响所有实例。
  • 实例属性 (Instance Attributes):

    • __init__ 方法中通过 self.attribute_name = value 定义。
    • 属于每个独立的实例,每个实例都有自己的一份。
    • 用于存储每个对象特有的数据。
    • 只能通过 instance_name.attribute_name 访问。
    • 修改实例属性时,只影响当前的实例。
class Dog:
    species = "Canis familiaris" # 类属性

    def __init__(self, name, age):
        self.name = name # 实例属性
        self.age = age   # 实例属性

dog1 = Dog("Buddy", 3)
dog2 = Dog("Lucy", 5)

print(f"Dog1 的名字: {dog1.name}, 年龄: {dog1.age}, 物种: {dog1.species}")
print(f"Dog2 的名字: {dog2.name}, 年龄: {dog2.age}, 物种: {dog2.species}")

# 改变类属性会影响所有实例 (以及通过类名访问)
Dog.species = "Canis lupus familiaris" # 假设是更精确的学名

print(f"Dog1 的新物种: {dog1.species}") # 跟着变化
print(f"Dog2 的新物种: {dog2.species}") # 跟着变化

# 改变实例属性只影响当前实例
dog1.name = "Bud"
print(f"Dog1 的新名字: {dog1.name}")
print(f"Dog2 的名字 (未变): {dog2.name}")

输出:

Dog1 的名字: Buddy, 年龄: 3, 物种: Canis familiaris
Dog2 的名字: Lucy, 年龄: 5, 物种: Canis familiaris
Dog1 的新物种: Canis lupus familiaris
Dog2 的新物种: Canis lupus familiaris
Dog1 的新名字: Bud
Dog2 的名字 (未变): Lucy

5. self 的重要性

self 是一个约定俗成的名称,它代表了当前正在操作的实例本身。在类的方法中,任何时候你需要访问或修改当前实例的属性,或者调用当前实例的其他方法,都必须通过 self. 来进行。

Python 会在调用实例方法时自动将实例作为第一个参数传递给 self

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        # 访问当前实例的 name 和 age 属性
        print(f"大家好,我叫 {self.name},我今年 {self.age} 岁了。")

    def celebrate_birthday(self):
        self.age += 1 # 修改当前实例的 age 属性
        print(f"{self.name} 庆祝了生日,现在 {self.age} 岁了!")
        self.introduce() # 调用当前实例的另一个方法

p1 = Person("张三", 20)
p1.introduce()
p1.celebrate_birthday()

输出:

大家好,我叫 张三,我今年 20 岁了。
张三 庆祝了生日,现在 21 岁了!
大家好,我叫 张三,我今年 21 岁了。

总结

本篇我们深入探讨了 Python 面向对象编程的基础:

  • 类 (Class): 对象的蓝图或模板,定义了对象的属性(数据)和行为(方法)。使用 class 关键字定义。
  • 对象 (Object) 或实例 (Instance): 类的具体化,是基于类创建的真实存在的“事物”。通过 ClassName() 来创建。
  • __init__ 方法: 特殊的构造器,在创建对象时自动调用,用于初始化对象的实例属性。
  • self 参数: 实例方法中的第一个参数,指向当前正在操作的对象本身。
  • 类属性: 属于类本身,所有实例共享。
  • 实例属性: 属于每个独立的对象,每个对象独有。
  • 通过点 . 运算符访问属性和调用方法。

理解类和对象是迈向编写大型、复杂、可维护的 Python 程序的关键一步。它能帮助你更好地组织代码,模拟现实世界的实体,并构建出更具结构和逻辑的应用程序。

练习题

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

  1. 定义一个 Book 类:

    • 定义一个名为 Book 的类。
    • 在类中定义一个类属性 material = "paper"
    • 定义 __init__ 方法,接收 title (书名), author (作者), pages (页数) 作为参数,并将其存储为实例属性。
    • 定义一个实例方法 get_summary(),打印书的标题、作者和页数,格式为 "《[书名]》 作者: [作者], 共 [页数] 页。"
  2. 创建和使用 Book 对象:

    • 创建两个 Book 类的对象:
      • 一本名为 “Python编程入门” 的书,作者 “A. Programmer”,500 页。
      • 一本名为 “数据结构与算法” 的书,作者 “B. Coder”,800 页。
    • 分别调用这两个对象的 get_summary() 方法。
    • 访问其中一本书的 material 属性,并打印。
  3. 修改实例属性和类属性的影响:

    • Book 类中,将 material 类属性改为 "recycled paper"
    • 再次打印两本书的 material 属性,观察变化。
    • 修改其中一本 Book 对象的 pages 属性(例如,将页数从 500 改为 550)。
    • 再次调用该对象的 get_summary() 方法,观察变化。另一本书的页数是否受影响?
  4. 定义一个 Student 类:

    • 定义一个名为 Student 的类。
    • 定义一个类属性 school_name = "Python University"
    • 定义 __init__ 方法,接收 name (学生姓名), student_id (学号), grades (一个表示各科分数的列表,默认空列表 [])。注意默认参数的陷阱! 请使用正确的姿势处理 grades 参数的默认值。
    • 定义一个实例方法 add_grade(grade),用于向学生的 grades 列表中添加一个分数。
    • 定义一个实例方法 get_average_grade(),计算并返回学生的平均分。如果 grades 列表为空,返回 0。
    • 定义一个实例方法 display_student_info(),打印学生姓名、学号、学校名称和平均分。
  5. 创建和操作 Student 对象:

    • 创建两个 Student 对象:
      • 学生 A: 姓名 “李华”, 学号 “2023001”。
      • 学生 B: 姓名 “王明”, 学号 “2023002”,初始分数 [70, 80, 90]。
    • 为学生 A 添加分数:85, 92, 78。
    • 分别调用两个学生的 display_student_info() 方法。
    • 修改 Student 类的 school_name 为 “Global Python University”。
    • 再次调用学生 A 的 display_student_info() 方法,观察学校名称是否变化。

练习题答案

1. 定义一个 Book 类:

# 1. 定义一个 Book 类
class Book:
    """表示一本书的类。"""
    material = "paper" # 类属性

    def __init__(self, title, author, pages):
        """
        初始化 Book 对象。
        Args:
            title (str): 书的标题。
            author (str): 书的作者。
            pages (int): 书的页数。
        """
        self.title = title
        self.author = author
        self.pages = pages

    def get_summary(self):
        """打印书的标题、作者和页数。"""
        print(f"《{self.title}》 作者: {self.author}, 共 {self.pages} 页。")

2. 创建和使用 Book 对象:

# 2. 创建和使用 Book 对象
book1 = Book("Python编程入门", "A. Programmer", 500)
book2 = Book("数据结构与算法", "B. Coder", 800)

book1.get_summary()
book2.get_summary()

# 访问 material 类属性
print(f"Book1 的材质是: {book1.material}")
print(f"Book2 的材质是: {book2.material}")
print(f"Book 类的材质是: {Book.material}")

输出:

《Python编程入门》 作者: A. Programmer, 共 500 页。
《数据结构与算法》 作者: B. Coder, 共 800 页。
Book1 的材质是: paper
Book2 的材质是: paper
Book 类的材质是: paper

3. 修改实例属性和类属性的影响:

# 3. 修改实例属性和类属性的影响
# 修改类属性
Book.material = "recycled paper"
print(f"\n修改 Book.material 后:")
print(f"Book1 的材质: {book1.material}") # 跟着变化
print(f"Book2 的材质: {book2.material}") # 跟着变化
print(f"Book 类的材质: {Book.material}")

# 修改实例属性
book1.pages = 550 # 只修改 book1 的页数
print(f"\n修改 book1.pages 后:")
book1.get_summary() # book1 的页数已改变
book2.get_summary() # book2 的页数未受影响

输出:

修改 Book.material 后:
Book1 的材质: recycled paper
Book2 的材质: recycled paper
Book 类的材质: recycled paper

修改 book1.pages 后:
《Python编程入门》 作者: A. Programmer, 共 550 页。
《数据结构与算法》 作者: B. Coder, 共 800 页。

4. 定义一个 Student 类:

# 4. 定义一个 Student 类
class Student:
    """表示一个学生的类。"""
    school_name = "Python University" # 类属性

    def __init__(self, name, student_id, grades=None): # 正确处理可变默认参数
        """
        初始化 Student 对象。
        Args:
            name (str): 学生姓名。
            student_id (str): 学号。
            grades (list, optional): 学生各科分数的列表。默认为 None。
        """
        self.name = name
        self.student_id = student_id
        # 如果 grades 是 None,则初始化一个空列表;否则使用传入的列表
        self.grades = [] if grades is None else list(grades) # 使用 list(grades) 避免共享同一个列表

    def add_grade(self, grade):
        """向学生的成绩列表中添加一个分数。"""
        if isinstance(grade, (int, float)):
            self.grades.append(grade)
        else:
            print("警告: 成绩必须是数字。")

    def get_average_grade(self):
        """计算并返回学生的平均分。"""
        if not self.grades: # 如果列表为空
            return 0
        return sum(self.grades) / len(self.grades)

    def display_student_info(self):
        """打印学生姓名、学号、学校名称和平均分。"""
        average = self.get_average_grade()
        print(f"\n--- 学生信息 ---")
        print(f"姓名: {self.name}")
        print(f"学号: {self.student_id}")
        print(f"学校: {Student.school_name}") # 通过类名访问类属性
        print(f"平均分: {average:.2f}") # 保留两位小数
        print("-" * 15)

5. 创建和操作 Student 对象:

# 5. 创建和操作 Student 对象
# 学生 A: 姓名 "李华", 学号 "2023001"。
student_a = Student("李华", "2023001")

# 学生 B: 姓名 "王明", 学号 "2023002",初始分数 [70, 80, 90]。
student_b = Student("王明", "2023002", [70, 80, 90])

# 为学生 A 添加分数
student_a.add_grade(85)
student_a.add_grade(92)
student_a.add_grade(78)

# 分别调用两个学生的 display_student_info() 方法
student_a.display_student_info()
student_b.display_student_info()

# 修改 Student 类的 school_name
Student.school_name = "Global Python University"
print("\n--- 修改学校名称后 ---")

# 再次调用学生 A 的 display_student_info() 方法,观察学校名称是否变化
student_a.display_student_info()
student_b.display_student_info() # 同样也受影响

输出:

--- 学生信息 ---
姓名: 李华
学号: 2023001
学校: Python University
平均分: 85.00
---------------

--- 学生信息 ---
姓名: 王明
学号: 2023002
学校: Python University
平均分: 80.00
---------------

--- 修改学校名称后 ---

--- 学生信息 ---
姓名: 李华
学号: 2023001
学校: Global Python University
平均分: 85.00
---------------

--- 学生信息 ---
姓名: 王明
学号: 2023002
学校: Global Python University
平均分: 80.00
---------------

网站公告

今日签到

点亮在社区的每一天
去签到