程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Python 基础

    • Python基础
  • Python 进阶

    • 装饰器与生成器
    • 异常处理
    • 标准库精讲
    • 模块与包
    • pip包管理工具
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
前端 (opens new window)
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Python 基础

    • Python基础
  • Python 进阶

    • 装饰器与生成器
    • 异常处理
    • 标准库精讲
    • 模块与包
    • pip包管理工具
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
前端 (opens new window)
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Python 基础

    • 环境搭建
    • 标识符
    • 变量
    • 缩进和注释
    • 数据类型
    • 数据类型转换
    • 运算符
    • 字符串
    • 列表
    • 元组
    • 字典
    • 集合
    • if判断
    • for循环
    • while循环
    • 循环综合练习
    • Python函数
    • 函数与循环实战
    • 生成式
    • 文件读写
    • 面向对象
      • 1. OOP 核心思想:类与对象
      • 2. 创建你的第一个类
      • 3. 对象的“出生证明”:__init__ 构造方法
      • 4. 属性:对象的数据
      • 5. 方法:对象的行为
        • 5.1 实例方法 (Instance Method)
        • 5.2 类方法 (Class Method)
        • 5.3 静态方法 (Static Method)
        • 5.4 三种方法对比总结
      • 6. OOP 三大支柱之一:封装 (Encapsulation)
        • 6.1 _ 和 __ 命名约定:访问控制的信号
        • 6.1.1 单下划线前缀 _ (受保护成员)
        • 6.1.2 双下划线前缀 __ (私有成员)
        • 6.2 Pythonic 封装:@property 装饰器
        • 6.2.1 创建只读属性
        • 6.2.2 创建可写属性 (Getter 和 Setter)
        • 6.2.3 完整的 Property: Getter, Setter, Deleter
      • 7. OOP 三大支柱之二:继承 (Inheritance)
        • 7.1 继承的基础语法
        • 7.2 方法重写与 super() 调用
        • 7.2.1 使用 super() 扩展 __init__ 方法
        • 7.2.2 使用 super() 扩展普通方法
        • 7.3 多重继承与 MRO
        • 7.4 内置函数 isinstance() 和 issubclass()
      • 8. OOP 三大支柱之三:多态 (Polymorphism)
        • 8.1 核心思想:鸭子类型 (Duck Typing)
        • 8.2 多态的实践
        • 8.3 多态的优势
        • 8.4 更安全的鸭子类型:hasattr
        • 8.5 抽象基类 (ABC) - 规范化的多态 (可选)
      • 9. 强大的魔术方法 (Magic Methods)
        • 9.1 对象的字符串表示:__str__ vs __repr__
        • 9.2 容器类魔术方法
        • 9.3 比较运算符
        • 9.4 可调用对象:__call__
      • 10. 面向对象的优势总结
    • 面向对象综合案例
  • Python 进阶

  • Python
  • Python 基础
scholar
2025-07-20
目录

面向对象

# 面向对象编程 (OOP)

面向对象编程 (Object-Oriented Programming, OOP) 是一种强大的编程范式,它将世界看作是由一个个独立的、相互交互的“对象”组成的。Python 从设计之初就是一门面向对象的语言,理解 OOP 是从入门到精通的关键一步。


# 1. OOP 核心思想:类与对象

想象一下,你要建造一栋房子。你首先需要一张设计蓝图,上面规定了房子有几扇窗、几扇门、墙壁的材质等。这张蓝图就是 类 (Class)。它定义了这类事物潜在的属性和可以执行的行为。

根据这张蓝图,你可以建造出许多栋实体房子。每一栋房子都是一个独立的存在,有自己的地址、颜色。这些实体房子就是 对象 (Object),也叫实例 (Instance)。每个对象都有类定义的属性和行为,但属性可以有具体的值(如 窗户数量 = 10)。

  • 类 (Class): 创建对象的模板或蓝图。它定义了一类事物共有的属性 (Attributes) 和方法 (Methods)。
  • 对象 (Object): 类的具体实例。它拥有类所定义的属性和方法,并且可以有自己独特的状态(属性值)。

# 2. 创建你的第一个类

在 Python 中,我们使用 class 关键字来定义一个类。类名通常遵循 驼峰命名法 (PascalCase),这是为了将其与使用 snake_case 的函数和变量区分开。

# class 关键字开始了类的定义,后面跟着类名 Dog
class Dog:
    # pass 是一个占位符,表示这个类的主体暂时是空的
    pass

# 实例化:调用类名就像调用函数一样,创建了一个 Dog 类的实例
# Python 在内存中为 dog1 分配了一块空间
dog1 = Dog()

print(type(dog1))  # 输出: <class '__main__.Dog'>
print(dog1)        # 输出: <__main__.Dog object at 0x...> (一个内存地址)
1
2
3
4
5
6
7
8
9
10
11

底层发生了什么? 当你调用 Dog() 时,Python 实际上执行了两步:

  1. 调用 __new__ 方法(我们通常不重写它),在内存中创建一个空的 Dog 对象。
  2. 调用 __init__ 方法,并将第一步创建的对象作为 self 参数传入,来初始化这个对象。

# 3. 对象的“出生证明”:__init__ 构造方法

__init__ 方法是一个特殊的“魔术方法”,在创建对象时自动被调用,用于为新创建的对象设置初始状态,即给它的属性赋初始值。

class Dog:
    # __init__ 方法,也叫构造方法或初始化方法
    def __init__(self, name, age):
        # 这里的 self 是一个指向刚被创建的实例的引用
        print(f"初始化一只名叫 {name} 的狗狗...")
        
        # self.name = name 的含义是:
        # “在这个实例(self)上,创建一个名为 'name' 的属性,
        #  并将传入的局部变量 name 的值赋给它。”
        self.name = name # 实例属性
        self.age = age   # 实例属性

my_dog = Dog("旺财", 2)

print(f"我的狗叫 {my_dog.name},它今年 {my_dog.age} 岁了。")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

self 是一个约定俗成的名称,它不是关键字。它永远是类中实例方法的第一个参数。

  • 作用: 代表实例本身。在类的方法内部,通过 self 可以访问该实例的任何属性和调用其任何方法。
  • 自动传递: 当你调用 my_dog.some_method(arg1) 时,Python 解释器会自动将其转换为 Dog.some_method(my_dog, arg1)。my_dog 实例被自动作为第一个参数 self 传入。

# 4. 属性:对象的数据

  • 实例属性 (Instance Attribute): 每个实例独有的数据。在 __init__ 方法中通过 self.attribute_name 定义。
  • 类属性 (Class Attribute): 整个类共享的数据。直接在类定义下声明。通常用于定义该类所有实例都应具备的共性特征或常量。
class Dog:
    # 类属性:所有 Dog 实例共享
    species = "犬科"

    def __init__(self, name):
        self.name = name

d1 = Dog("大黄")
d2 = Dog("小白")

# 属性查找顺序:Python 会先在实例自己的属性中查找,找不到再去类属性中查找。
print(f"d1 的物种是: {d1.species}") # d1 实例没有 species 属性,向上找到 Dog.species

# 实例属性的独立性
d1.name = "阿黄"
print(f"d1 的名字是 {d1.name},d2 的名字是 {d2.name}。") # d1 的修改不影响 d2

# 类属性的共享性
Dog.species = "Canis lupus familiaris"
print(f"d1 的物种更新为 {d1.species},d2 的也更新为 {d2.species}。")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

属性遮蔽 (Attribute Shadowing): 如果你给一个实例设置了与类属性同名的属性,那么实例属性会“遮蔽”类属性。

d1.species = "中华田园犬" # 这是在 d1 实例上创建了一个新的实例属性
print(f"d1 的物种: {d1.species}")   # 输出: 中华田园犬 (实例属性)
print(f"d2 的物种: {d2.species}")   # 输出: Canis lupus familiaris (类属性)
print(f"类的物种: {Dog.species}")   # 输出: Canis lupus familiaris (类属性)
1
2
3
4

# 5. 方法:对象的行为

方法(Methods)是定义在类中的函数,用于封装和实现对象的行为。根据方法与类和实例的关联方式,Python 中的方法主要分为三种:实例方法、类方法和静态方法。

# 5.1 实例方法 (Instance Method)

这是最常见的方法类型。它与类的单个实例紧密绑定,能够访问和修改该实例的状态(即实例属性)。

  • 定义: 方法的第一个参数必须是 self,它代表调用该方法的实例对象。
  • 调用: 通过实例来调用,例如 my_instance.method_name()。
  • 核心用途: 操作实例属性。几乎所有需要读取或修改单个对象状态的操作,都应通过实例方法完成。
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    # 这是一个实例方法
    def show_score(self):
        # self 自动指向调用此方法的实例 (例如下面的 'student_a')
        # 通过 self 可以访问这个实例的 name 和 score 属性
        print(f"学生 {self.name} 的分数是 {self.score}")

    # 这也是一个实例方法,它修改了实例的状态
    def update_score(self, new_score):
        print(f"学生 {self.name} 的分数从 {self.score} 更新为 {new_score}")
        self.score = new_score

# 创建一个实例
student_a = Student("小明", 85)

# 调用实例方法
# Python 自动将调用者 student_a 作为 self 参数传入 show_score
student_a.show_score()  # 等价于 Student.show_score(student_a)

student_a.update_score(92)
student_a.show_score()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 5.2 类方法 (Class Method)

类方法与整个类相关联,而不是与类的某个特定实例相关联。它通常用于处理与类本身相关的逻辑,如读取或修改类属性,或者提供备用的构造方式。

  • 定义: 必须使用 @classmethod 装饰器。方法的第一个参数必须是 cls,它代表类本身。
  • 调用: 可以通过类名直接调用(ClassName.method_name()),也可以通过实例调用(但仍然操作的是类级别的状态)。
  • 核心用途:
    1. 访问或修改类属性。
    2. 创建备用构造器 (Alternative Constructors):这是类方法最经典、最有用的场景。
import datetime

class Person:
    # 类属性
    species = "智人"

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

    # 一个普通的实例方法
    def display(self):
        print(f"{self.name}, 年龄 {self.age}, 物种: {self.species}")

    # 一个类方法,用于创建实例
    # 比如我们希望可以从一个出生年份来创建 Person 对象
    @classmethod
    def from_birth_year(cls, name, birth_year):
        # cls 在这里就是 Person 类本身
        # cls(...) 就等同于 Person(...)
        current_year = datetime.date.today().year
        age = current_year - birth_year
        # 返回一个通过计算得出的新实例
        return cls(name, age)

# --- 使用 ---
# 正常创建实例
person_1 = Person("张三", 30)
person_1.display()

# 使用类方法这个“备用构造器”来创建实例
person_2 = Person.from_birth_year("李四", 1990)
person_2.display()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

在这个例子中,from_birth_year 提供了一种比 __init__ 更直观、更具描述性的方式来创建对象,增强了类的易用性。

# 5.3 静态方法 (Static Method)

静态方法在功能上与类完全独立,它既不访问实例状态(self),也不访问类状态(cls)。它就像一个恰好被放在类这个“命名空间”里的普通函数。

  • 定义: 必须使用 @staticmethod 装饰器。方法没有 self 或 cls 这样的强制性首个参数。
  • 调用: 可以通过类名或实例名调用。
  • 核心用途: 作为工具函数(Utility Function)。当某个功能在逻辑上与一个类相关,但其实现又完全不需要类或实例的任何信息时,就应使用静态方法。
class MathUtils:
    # 这是一个静态方法,它只是一个逻辑上相关的工具函数
    @staticmethod
    def circle_area(radius):
        # 这个计算不依赖于任何 MathUtils 的实例或类属性
        return 3.14159 * (radius ** 2)

    @staticmethod
    def is_positive(number):
        return number > 0

# --- 使用 ---
# 无需创建实例,直接通过类名调用
area = MathUtils.circle_area(5)
print(f"半径为5的圆面积是: {area}")

print(f"10 是正数吗? {MathUtils.is_positive(10)}")
print(f"-5 是正数吗? {MathUtils.is_positive(-5)}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 5.4 三种方法对比总结

特性 实例方法 (Instance Method) 类方法 (Class Method) 静态方法 (Static Method)
装饰器 无 @classmethod @staticmethod
第一个参数 self (代表实例) cls (代表类) 无特殊参数
核心用途 操作实例的状态(实例属性) 操作类的状态(类属性),或创建备用构造器 逻辑上相关的工具函数
访问能力 可访问实例属性和类属性 只能访问类属性 不能访问实例属性或类属性
调用方式 instance.method() Class.method() 或 instance.method() Class.method() 或 instance.method()

# 6. OOP 三大支柱之一:封装 (Encapsulation)

封装是面向对象编程的一个核心概念,其主旨是将数据(属性)和操作数据的代码(方法)捆绑在一起,并对外部隐藏对象的内部实现细节。

想象一下电视机。作为用户,你只需要使用遥控器(公开接口)来开关、换台、调音量。你不需要(也不应该)打开电视外壳,去直接操作里面的电路板(内部状态)。封装就是这层“外壳”,它保护了内部状态不被随意修改,同时提供了简单、安全的交互方式。

在 Python 中,我们主要通过命名约定和 @property 装饰器来实现封装。

# 6.1 _ 和 __ 命名约定:访问控制的信号

Python 没有像 Java 或 C++ 那样严格的 private 或 public 关键字。相反,它依赖于程序员之间的约定,通过在属性或方法名前添加下划线来表示其可见性。

# 6.1.1 单下划线前缀 _ (受保护成员)

  • 约定: _variable 或 _method()
  • 含义: 这是一个内部使用的属性或方法,它不应该被类的外部直接访问。这是一种“君子协定”,告诉其他开发者:“这是内部实现,如果你要用,请自己承担风险。”
  • 效果: Python 解释器不会做任何特殊处理。你在外部依然可以访问它,但这样做是不被推荐的。
class Person:
    def __init__(self, name, age):
        self.name = name     # 公开属性
        self._age = age      # 约定为受保护的属性

    def _get_birth_year(self): # 约定为受保护的方法
        return 2023 - self._age

p = Person("小明", 25)

# 虽然可以访问,但不推荐这样做
print(f"{p.name} 的年龄是 {p._age}") 
print(f"出生年份大约是 {p._get_birth_year()}")
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6.1.2 双下划线前缀 __ (私有成员)

  • 约定: __variable 或 __method()
  • 含义: 这是一个私有成员,旨在仅供类的内部使用。
  • 效果: Python 会对此类名称进行名称改写 (Name Mangling)。它会将 __name 这样的名字自动变为 _ClassName__name。
  • 目的: 这种机制的主要目的不是为了制造真正的“私有”,而是为了避免在子类中意外地覆盖父类的私有成员,从而增强类的独立性。
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance  # 私有属性

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            self.__log_transaction(f"存入 {amount}")

    def get_balance(self):
        # 在类的内部,可以直接使用 __balance
        return self.__balance

    def __log_transaction(self, message): # 私有方法
        print(f"交易记录: {message}, 当前余额: {self.__balance}")

acc = BankAccount("Alice", 1000)

# 外部无法直接访问私有成员,会报错
# print(acc.__balance)  # AttributeError: 'BankAccount' object has no attribute '__balance'
# acc.__log_transaction("尝试记录") # AttributeError

# 只能通过公开的接口 (public method) 来与对象交互
acc.deposit(500)
print(f"Alice 的余额是: {acc.get_balance()}")

# 名称改写后的名字仍然可以访问(强烈不推荐)
print(f"通过名称改写访问余额: {acc._BankAccount__balance}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 6.2 Pythonic 封装:@property 装饰器

虽然命名约定很有用,但更优雅、更符合 Python 哲学的封装方式是使用 @property 装饰器。它能让你将一个方法伪装成一个属性来使用,使得代码更简洁,同时还能在背后执行复杂的逻辑(如计算、验证)。

@property 主要用于以下场景:

  • 只读属性:属性的值由其他属性计算得出,不允许直接修改。
  • 数据验证:在给属性赋值时,检查新值的有效性。

# 6.2.1 创建只读属性

@property 最简单的用法是创建一个只读属性。你只需要定义一个 getter 方法。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        """
        这个方法被 @property 装饰后,就变成了一个名为 'area' 的只读属性。
        每次访问 rect.area 时,这个方法都会被自动调用。
        """
        return self.width * self.height

# --- 使用 ---
r = Rectangle(10, 5)

# 像访问普通属性一样访问 area,背后其实是调用了 area() 方法
print(f"矩形的面积是: {r.area}")  # 输出: 50

# 修改构成 area 的基础属性
r.width = 12
# area 属性的值会自动重新计算
print(f"修改宽度后,面积是: {r.area}") # 输出: 60

# 尝试直接给 area 赋值会失败,因为它是一个只读属性
# r.area = 100  # 这会引发 AttributeError: can't set attribute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 6.2.2 创建可写属性 (Getter 和 Setter)

要让一个 property 属性变得可写,我们需要为它定义一个 setter 方法。这是实现数据验证的最佳位置。

语法结构:

  1. 用 @property 装饰 getter 方法,方法名将成为公开的属性名。
  2. 用 @属性名.setter 装饰 setter 方法。
  3. 在内部,通常会有一个以下划线开头的“真实”属性来存储值。
class Student:
    def __init__(self, name):
        self.name = name
        self._score = 0  # 内部属性,用于存储真实的分数

    @property
    def score(self):
        """Getter: 当访问 student.score 时调用"""
        print(f"正在获取 {self.name} 的分数...")
        return self._score

    @score.setter
    def score(self, value):
        """Setter: 当 student.score = value 赋值时调用"""
        print(f"正在为 {self.name} 设置分数...")
        if not (0 <= value <= 100):
            # 在 setter 中进行数据验证
            raise ValueError("分数必须在 0 到 100 之间!")
        self._score = value

# --- 使用 ---
s = Student("小华")

# 像普通属性一样赋值,背后调用了 setter 方法
s.score = 95  # 输出: 正在为 小华 设置分数...

# 像普通属性一样访问,背后调用了 getter 方法
print(f"{s.name} 的分数是 {s.score}") # 输出: 正在获取 小华 的分数...  小华 的分数是 95

# 尝试赋一个无效值,setter 中的验证逻辑会起作用
try:
    s.score = 120
except ValueError as e:
    print(f"赋值失败: {e}") # 输出: 赋值失败: 分数必须在 0 到 100 之间!

# 再次查看分数,确认它没有被修改
print(f"当前分数依然是: {s.score}") # 输出: ... 当前分数依然是: 95
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 6.2.3 完整的 Property: Getter, Setter, Deleter

property 还有一个删除器 (deleter),用于定义 del obj.attribute 时的行为。

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

    @property
    def email(self):
        """Getter for email"""
        return self._email

    @email.setter
    def email(self, value):
        """Setter for email"""
        if '@' not in value:
            raise ValueError("无效的邮箱地址")
        self._email = value

    @email.deleter
    def email(self):
        """Deleter for email"""
        print(f"正在删除用户邮箱: {self._email}")
        self._email = None

# --- 使用 ---
user = User("test@example.com")
print(f"当前邮箱: {user.email}")

user.email = "new.test@example.com"
print(f"新邮箱: {user.email}")

# 使用 del 关键字会触发 deleter
del user.email
# 输出: 正在删除用户邮箱: new.test@example.com

print(f"删除后的邮箱: {user.email}") # 输出: None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 7. OOP 三大支柱之二:继承 (Inheritance)

继承允许我们创建一个新类(称为子类或派生类),它会获取另一个类(称为父类、基类或超类)的所有属性和方法。这是实现代码重用和构建清晰层级关系(例如 “狗 是一种 动物”)的核心机制。

子类不仅拥有父类的所有功能,还可以:

  • 添加新功能:定义父类没有的新属性或新方法。
  • 重写已有功能:对父类的方法提供自己的实现版本。

# 7.1 继承的基础语法

在定义类时,将父类的名字放在类名后的括号中,即可实现继承。

# 定义一个父类 Animal
class Animal:
    def __init__(self, name):
        self.name = name
    
    def eat(self):
        print(f"{self.name} 正在吃东西。")

    def speak(self):
        # 使用 raise NotImplementedError 强制子类必须实现这个方法
        raise NotImplementedError("子类必须实现 speak 方法")

# Dog 类继承自 Animal 类
# 这意味着 Dog 自动拥有了 Animal 的 __init__ 和 eat 方法
class Dog(Animal):
    # 子类可以有自己的方法
    def fetch(self):
        print(f"{self.name} 跑去捡球了!")
    
    # 子类可以重写父类的方法
    def speak(self):
        return f"{self.name} 说:汪汪!"

# Cat 类也继承自 Animal
class Cat(Animal):
    def speak(self):
        return f"{self.name} 说:喵喵~"

# --- 使用 ---
my_dog = Dog("旺财")
my_cat = Cat("咪咪")

# 子类对象可以直接调用继承自父类的 eat 方法
my_dog.eat()  # 输出: 旺财 正在吃东西。
my_cat.eat()  # 输出: 咪咪 正在吃东西。

# 子类对象调用自己重写后的 speak 方法
print(my_dog.speak()) # 输出: 旺财 说:汪汪!
print(my_cat.speak()) # 输出: 咪咪 说:喵喵~

# 子类对象调用自己的新方法
my_dog.fetch() # 输出: 旺财 跑去捡球了!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 7.2 方法重写与 super() 调用

当子类的方法与父类的方法同名时,子类的方法会覆盖父类的方法,这称为方法重写 (Method Overriding)。

但有时我们不想完全覆盖,而是想在父类方法的基础上增加一些新功能。这时,就需要 super() 函数来帮助我们调用父类中被重写的方法。

# 7.2.1 使用 super() 扩展 __init__ 方法

一个非常常见的场景是,子类在自己的 __init__ 中需要先完成父类的初始化。

重要:如果你在子类中定义了 __init__ 方法,Python 不会再自动调用父类的 __init__ 方法。你必须手动使用 super().__init__(...) 来调用它。

class Animal:
    def __init__(self, name):
        print(f"【父类 Animal 初始化】为 '{name}' 分配名字。")
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        # 1. 使用 super() 调用父类的 __init__ 方法
        #    这样就完成了 self.name = name 的初始化
        print(f"【子类 Dog 初始化开始】准备调用父类初始化...")
        super().__init__(name)
        
        # 2. 然后再添加子类自己的初始化逻辑
        print(f"【子类 Dog 初始化】为 '{name}' 分配品种。")
        self.breed = breed # 这是 Dog 类独有的属性

# 当创建 Dog 实例时,观察输出顺序
d = Dog("小白", "萨摩耶")
print(f"狗的名字: {d.name}, 品种: {d.breed}")

# 输出:
# 【子类 Dog 初始化开始】准备调用父类初始化...
# 【父类 Animal 初始化】为 '小白' 分配名字。
# 【子类 Dog 初始化】为 '小白' 分配品种。
# 狗的名字: 小白, 品种: 萨摩耶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 7.2.2 使用 super() 扩展普通方法

同样地,你也可以用 super() 来扩展任何被重写的普通方法。

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def get_details(self):
        return f"姓名: {self.name}, 薪水: {self.salary}"

class Manager(Employee):
    def __init__(self, name, salary, department):
        super().__init__(name, salary)
        self.department = department
        
    def get_details(self):
        # 先调用父类的 get_details 方法获取基本信息
        base_details = super().get_details()
        # 然后再附加子类自己的信息
        return f"{base_details}, 部门: {self.department}"

manager = Manager("王经理", 20000, "技术部")
print(manager.get_details()) 
# 输出: 姓名: 王经理, 薪水: 20000, 部门: 技术部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 7.3 多重继承与 MRO

一个子类可以同时继承多个父类,这就是多重继承。

class Father:
    def skill_f(self):
        print("会做饭")
    def common_skill(self):
        print("Father's skill")

class Mother:
    def skill_m(self):
        print("会织毛衣")
    def common_skill(self):
        print("Mother's skill")

# Child 同时继承 Father 和 Mother
# 继承顺序 (Father, Mother) 很重要!
class Child(Father, Mother):
    pass

c = Child()
c.skill_f()      # 输出: 会做饭
c.skill_m()      # 输出: 会织毛衣
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

当多个父类有同名方法时(如 common_skill),Python 会根据方法解析顺序 (Method Resolution Order, MRO) 来决定调用哪一个。MRO 遵循“从左到右,深度优先”的原则。

c.common_skill() # 输出: Father's skill (因为 Father 在继承列表的左边)

# 我们可以打印出 MRO 列表来查看查找顺序
print(Child.mro())
# 输出: 
# [<class '__main__.Child'>,   # 1. 先找自己
#  <class '__main__.Father'>,   # 2. 再找第一个父类 Father
#  <class '__main__.Mother'>,   # 3. 再找第二个父类 Mother
#  <class 'object'>]           # 4. 最后找所有类的基类 object
1
2
3
4
5
6
7
8
9

注意:多重继承虽然强大,但也可能导致代码结构复杂、难以理解(菱形继承问题)。在实际开发中应谨慎使用。

# 7.4 内置函数 isinstance() 和 issubclass()

  • isinstance(obj, Class): 检查一个对象是否是某个类或其子类的实例。
  • issubclass(Sub, Super): 检查一个类是否是另一个类的子类。
my_dog = Dog("旺财")

print(f"my_dog 是 Dog 类的实例吗? {isinstance(my_dog, Dog)}")       # True
print(f"my_dog 是 Animal 类的实例吗? {isinstance(my_dog, Animal)}") # True (因为 Dog 是 Animal 的子类)
print(f"my_dog 是 Cat 类的实例吗? {isinstance(my_dog, Cat)}")       # False

print(f"Dog 是 Animal 的子类吗? {issubclass(Dog, Animal)}")         # True
print(f"Animal 是 Dog 的子类吗? {issubclass(Animal, Dog)}")         # False
1
2
3
4
5
6
7
8

# 8. OOP 三大支柱之三:多态 (Polymorphism)

多态,字面意思为“多种形态”。在编程中,它指的是不同的对象在接收到同一个消息(调用同一个方法)时,能够表现出不同的行为。

想象一个“USB接口”,它就是一个统一的规范。无论你插入的是U盘、键盘还是鼠标(不同的对象),电脑(调用者)都通过同一个USB接口(统一的方法)与它们交互,但每个设备都会执行自己独特的功能(不同的行为)。这就是多态。

在 Python 中,多态的核心实现方式是鸭子类型。

# 8.1 核心思想:鸭子类型 (Duck Typing)

鸭子类型的核心思想是:

“如果一个东西走起来像鸭子,叫起来也像鸭子,那么它就是一只鸭子。”

换句话说,我们不关心一个对象的具体类型是什么,只关心它是否具有我们需要的行为(即方法)。如果两个类都有一个 .speak() 方法,那么我们就可以把它们都当作会“说话”的东西来对待,而无需关心一个是 Dog,另一个是 Cat。

Python 的多态是建立在动态类型之上的,它不要求强制的继承关系。只要对象“看起来”有我们需要的方法,就可以正常工作。

# 8.2 多态的实践

让我们来看一个具体的例子。我们将定义几个完全不相关的类,但它们都恰好有一个名为 make_sound 的方法。

# 定义几个不同的类
class Dog:
    def make_sound(self):
        print("汪!汪汪!")

class Cat:
    def make_sound(self):
        print("喵~")

class Car:
    def make_sound(self):
        print("嘀嘀嘀!")

# 定义一个统一的接口函数
# 这个函数不关心传入的 a_thing 是什么类型
# 它只假设 a_thing 有一个 .make_sound() 方法
def let_it_make_sound(a_thing):
    a_thing.make_sound()

# 创建不同类的实例
dog = Dog()
cat = Cat()
car = Car()

# 将这些不同类型的对象传入同一个函数
let_it_make_sound(dog)  # 输出: 汪!汪汪!
let_it_make_sound(cat)  # 输出: 喵~
let_it_make_sound(car)  # 输出: 嘀嘀嘀!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

在这个例子中,let_it_make_sound 函数就是多态的体现。它接受任何有 .make_sound() 方法的对象,并成功地执行了操作,展示了代码的极高灵活性。

我们甚至可以对一个包含不同类型对象的列表进行迭代,并统一调用它们的方法:

entities = [Dog(), Cat(), Car()]

for entity in entities:
    entity.make_sound()
1
2
3
4

# 8.3 多态的优势

  1. 代码更灵活:我们可以随时创建新的类,只要它实现了约定的方法(如 make_sound),就能无缝地集成到现有代码中,而无需修改任何调用它的函数。
  2. 接口更统一:多态允许我们忽略对象的类型差异,编写出更通用、更抽象的代码。
  3. 可扩展性强:当需要添加新功能时,通常只需要增加一个新的、符合“鸭子类型”规范的类即可。

# 8.4 更安全的鸭子类型:hasattr

纯粹的鸭子类型依赖于“信任”,即我们假设传入的对象一定有我们需要的方法。如果传入一个没有该方法的对象,程序就会在运行时出错。

class Book:
    def read(self):
        print("正在阅读...")

# book = Book()
# let_it_make_sound(book) # 会报错:AttributeError: 'Book' object has no attribute 'make_sound'
1
2
3
4
5
6

为了让代码更健壮,我们可以使用 hasattr() 函数在调用前进行检查。

def safe_let_it_make_sound(a_thing):
    if hasattr(a_thing, 'make_sound') and callable(a_thing.make_sound):
        a_thing.make_sound()
    else:
        print(f"对象 {type(a_thing).__name__} 没有 make_sound 方法")

book = Book()
safe_let_it_make_sound(dog)   # 输出: 汪!汪汪!
safe_let_it_make_sound(book)  # 输出: 对象 Book 没有 make_sound 方法
1
2
3
4
5
6
7
8
9

# 8.5 抽象基类 (ABC) - 规范化的多态 (可选)

虽然鸭子类型非常灵活,但在大型项目或框架设计中,有时我们希望对“接口”有一个更严格的定义。这时可以使用 abc (Abstract Base Classes) 模块。

抽象基类可以定义一组方法,并强制其所有子类必须实现这些方法。这就像是为“鸭子”制定了一个官方标准。

from abc import ABC, abstractmethod

# 定义一个抽象基类
class SoundMaker(ABC):
    @abstractmethod
    def make_sound(self):
        pass

# Dog 继承自 SoundMaker,并且必须实现 make_sound
class Dog(SoundMaker):
    def make_sound(self):
        print("汪!")

# 如果一个类继承了但没有实现抽象方法,实例化时会报错
class BrokenCat(SoundMaker):
    def meow(self): # 方法名不匹配
        pass

d = Dog()
d.make_sound() # 正确执行

# b = BrokenCat() # 会报错: TypeError: Can't instantiate abstract class BrokenCat with abstract method make_sound
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

使用抽象基类,可以在保持多态灵活性的同时,增加代码的健壮性和可维护性。


# 9. 强大的魔术方法 (Magic Methods)

魔术方法,官方称为特殊方法 (Special Methods),是 Python 中一类以双下划线开头和结尾的预定义方法(例如 __init__、__len__)。它们不是让你手动调用的,而是作为响应特定操作的“钩子”,由 Python 解释器在特定时刻自动触发。

通过正确地实现这些方法,你可以让自定义对象无缝集成到 Python 的语言特性中,使其行为像内建类型(如列表、字典)一样自然、强大。

# 9.1 对象的字符串表示:__str__ vs __repr__

这两个方法都用于返回对象的字符串表示,但目标和用途完全不同。

  • __str__(self):
    • 目标用户: 普通用户。
    • 触发时机: 当你对实例使用 print() 函数或 str() 类型转换时。
    • 目标: 可读性。应返回一个简洁、友好、易于理解的字符串。
  • __repr__(self):
    • 目标用户: 开发者。
    • 触发时机: 当你在交互式控制台中直接输入变量名、使用 repr() 函数,或在调试器中查看对象时。
    • 目标: 明确性和无歧义。理想情况下,__repr__ 返回的字符串应该是一个有效的 Python 表达式,可以通过 eval() 重新创建出该对象。
  • 最佳实践:
    • 同时实现这两个方法。
    • 如果 __str__ 未被定义,Python 会自动使用 __repr__ 作为替代。因此,至少要实现 __repr__。
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        # 对用户友好的输出
        return f"坐标点 ({self.x}, {self.y})"

    def __repr__(self):
        # 对开发者友好的、明确的输出
        # 这个字符串可以直接用来创建对象: Point(10, 20)
        return f"Point(x={self.x}, y={self.y})"

# --- 使用 ---
p = Point(10, 20)

# print() 会触发 __str__
print(p)  # 输出: 坐标点 (10, 20)

# str() 会触发 __str__
s = str(p)
print(s)  # 输出: 坐标点 (10, 20)

# 在交互式控制台输入 p 或使用 repr() 会触发 __repr__
print(repr(p)) # 输出: Point(x=10, y=20)

# __repr__ 的结果可以用来重新创建对象
p_clone = eval(repr(p))
print(p_clone) # 输出: 坐标点 (10, 20)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 9.2 容器类魔术方法

通过实现这些方法,你可以让你的对象表现得像一个容器(如列表或字典)。

  • __len__(self): 响应 len(obj) 操作。
  • __getitem__(self, key): 响应 obj[key] (获取元素) 操作。
  • __setitem__(self, key, value): 响应 obj[key] = value (设置元素) 操作。
  • __delitem__(self, key): 响应 del obj[key] (删除元素) 操作。
class ShoppingCart:
    def __init__(self):
        # 使用字典来存储商品和数量
        self._items = {}

    def add_item(self, item_name, quantity=1):
        self._items[item_name] = self._items.get(item_name, 0) + quantity

    def __len__(self):
        # 购物车的“长度”是商品的总数量
        return sum(self._items.values())

    def __getitem__(self, item_name):
        # 允许通过 cart['苹果'] 的方式获取商品数量
        return self._items.get(item_name, 0)

    def __setitem__(self, item_name, quantity):
        # 允许通过 cart['苹果'] = 5 的方式设置商品数量
        if quantity <= 0:
            # 如果数量小于等于0,则移除商品
            if item_name in self._items:
                del self._items[item_name]
        else:
            self._items[item_name] = quantity
            
    def __str__(self):
        if not self._items:
            return "购物车是空的"
        parts = [f"{name}: {qty}件" for name, qty in self._items.items()]
        return f"购物车中有 {len(self)} 件商品: {', '.join(parts)}"

# --- 使用 ---
cart = ShoppingCart()
cart.add_item("苹果", 2)
cart.add_item("香蕉")
print(cart)  # __str__ 被调用

# len() 触发 __len__
print(f"商品总数: {len(cart)}") # 输出: 3

# 索引访问触发 __getitem__
print(f"苹果的数量: {cart['苹果']}") # 输出: 2
print(f"西瓜的数量: {cart['西瓜']}") # 输出: 0

# 索引赋值触发 __setitem__
cart['香蕉'] = 5
cart['橙子'] = 10
print(f"更新后: {cart}")

# 赋值为0会移除商品
cart['橙子'] = 0
print(f"移除橙子后: {cart}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 9.3 比较运算符

  • __eq__(self, other): 响应 ==
  • __ne__(self, other): 响应 !=
  • __lt__(self, other): 响应 <
  • __gt__(self, other): 响应 >
  • __le__(self, other): 响应 <=
  • __ge__(self, other): 响应 >=
class Team:
    def __init__(self, name, points):
        self.name = name
        self.points = points
        
    def __str__(self):
        return f"球队 {self.name} ({self.points}分)"

    def __eq__(self, other):
        # 当两个 Team 对象的积分相同时,我们认为它们是“相等”的
        if not isinstance(other, Team):
            return NotImplemented
        return self.points == other.points

    def __gt__(self, other):
        # 定义“大于”的关系为积分更高
        if not isinstance(other, Team):
            return NotImplemented
        return self.points > other.points

team_a = Team("勇士", 105)
team_b = Team("湖人", 102)
team_c = Team("骑士", 105)

print(f"{team_a} > {team_b}: {team_a > team_b}") # True
print(f"{team_a} == {team_b}: {team_a == team_b}") # False
print(f"{team_a} == {team_c}: {team_a == team_c}") # True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 9.4 可调用对象:__call__

实现 __call__(self, *args, **kwargs) 方法可以让你的类的实例像函数一样被调用。

class Adder:
    def __call__(self, x, y):
        print(f"调用 Adder 实例,计算 {x} + {y}")
        return x + y

# 创建实例
add = Adder()

# 像调用函数一样调用实例
result = add(5, 10)  # 这会触发 Adder.__call__(add, 5, 10)
print(f"结果: {result}") # 输出: ... 结果: 15
1
2
3
4
5
6
7
8
9
10
11

这在创建需要维护状态的函数式对象时非常有用,例如在机器学习框架中,层的对象通常是可调用的。


# 10. 面向对象的优势总结

  1. 封装 (Encapsulation): 保护数据,隐藏实现细节。用户只需与安全的接口交互,无需关心内部复杂逻辑。
  2. 继承 (Inheritance): 重用代码,构建清晰的层级关系(“is a”关系),减少冗余。
  3. 多态 (Polymorphism): 提高代码的灵活性和可扩展性。你可以编写适用于多种对象类型的通用代码,而无需担心它们的具体实现。
  4. 抽象 (Abstraction): 通过类来模拟现实世界的问题,使代码更贴近业务逻辑,更易于理解和设计。
编辑此页 (opens new window)
上次更新: 2025/07/23, 07:48:51
文件读写
面向对象综合案例

← 文件读写 面向对象综合案例→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式