07面向对象

07面向对象

_

注:学习资料来源<尚硅谷>

视频:点击跳转

代码仓库:pythonStudy

面向对象编程(Object-Oriented Programming,OOP)是现代软件开发最重要的编程范式之一。它以一种更贴近人类认知世界的方式组织代码——将数据和行为封装在对象中,通过来描述对象的共性,通过继承实现代码复用,通过多态让不同对象对同一消息作出不同响应。Python 从设计之初就完全支持面向对象,并且在此基础上融入了动态语言的灵活性(如鸭子类型)。

本文将系统梳理 Python 面向对象的核心知识,涵盖类与实例、属性与方法、继承体系、权限控制、魔法方法、多态、抽象类以及一个完整的综合案例。无论你是有过其他语言 OOP 经验,还是刚刚接触这一概念,都能从中获得清晰且可落地的理解。


1. 为什么需要面向对象?

在面向过程的编程思维中,我们聚焦于“做事的步骤”,比如“做饭”这个过程:洗菜 → 切菜 → 炒菜 → 装盘。而当程序规模扩大,过程式的代码容易变得臃肿、难以维护和复用。

面向对象则切换视角:关注“谁来做这件事”。仍以做饭为例,我们会引入“切菜员”负责洗菜和切菜,“厨师”负责炒菜和装盘。这些切菜员和厨师就是对象,他们拥有各自的属性(姓名、工号)和行为(洗菜、切菜、炒菜)。这种思想天然支持模块化:切菜员可以服务于多个厨师,厨师也可以制作不同菜品;对象之间可以协作构成更复杂的系统。

在 Python 中,一切皆对象,数字、字符串、函数、模块乃至类本身都是对象。理解面向对象不仅能让你更好地使用 Python 标准库和第三方模块,更能帮助你在大型项目中编写高内聚、低耦合的优雅代码。


2. 类与实例:图纸与实物

2.1 类的定义

类(class) 是创建对象的模板,它定义了一类事物共有的属性行为。在 Python 中用 class 关键字定义类,类名通常采用大驼峰命名法(如 Person, ShoppingCart)。

类中最核心的方法是 __init__,称为初始化方法。每当我们根据类创建一个实例时,Python 会自动调用它,用于给新生的实例绑定初始属性。__init__ 的第一个参数必须是 self,代表当前正在被创建的实例对象。

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

2.2 创建实例与属性访问

“实例”就是根据类这个“图纸”制造出来的具体“实物”,这个过程称为实例化。创建实例的语法类似于函数调用:类名(参数...)

p1 = Person('张三', 25, '男')
p2 = Person('李四', 30, '女')

通过点语法可以访问或修改实例属性:

print(p1.name, p1.age)     # 张三 25
p1.age = 26                # 修改属性
print(p1.age)              # 26

我们还可以动态地给实例附加新的属性(但不推荐滥用,因为容易导致对象结构不可控):

p1.address = '北京'
print(p1.address)          # 北京

2.3 实例方法:定义对象的行为

类中定义的函数称为方法。实例方法的第一个参数同样是 self,代表调用该方法的实例对象。所有实例共享同一份方法定义(存放在类中),但通过 self 可以访问各自独立的属性。

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

    def introduce(self):
        print(f'我叫{self.name},今年{self.age}岁。')

    def run(self, distance):
        print(f'{self.name}跑了{distance}米。')

p = Person('王五', 22)
p.introduce()       # 我叫王五,今年22岁。
p.run(500)          # 王五跑了500米。

3. 属性的两种形态:实例属性与类属性

3.1 实例属性

通过 self.xxx = value__init__ 或其他实例方法中定义的属性属于实例属性。每个实例拥有自己独立的一份,互不影响。

3.2 类属性

直接在类体内部、方法外部定义的变量属于类属性。它被所有实例共享,常用于存储常量或全局配置。

class Person:
    max_age = 120          # 类属性
    planet = '地球'

    def __init__(self, name, age):
        self.name = name
        if age > Person.max_age:
            self.age = Person.max_age
        else:
            self.age = age

p1 = Person('张三', 150)
p2 = Person('李四', 30)
print(p1.age)              # 120
print(p2.age)              # 30

类属性可以通过类名或实例访问,但要注意:如果通过实例进行赋值操作(如 p1.planet = '火星'),实际上是在该实例上创建了一个同名的实例属性,而不会影响类属性本身。这是一个常见的陷阱。


4. 方法家族:实例方法、类方法和静态方法

Python 类中定义的方法按接收的参数不同,可分为三种:

类型

装饰器

第一个参数

推荐调用方式

典型用途

实例方法

self

实例调用

操作实例属性,定义对象行为

类方法

@classmethod

cls

类调用

操作类属性,工厂方法

静态方法

@staticmethod

无特殊参数

类调用

与类相关的工具函数

4.1 类方法

类方法使用 @classmethod 装饰,第一个参数 cls 代表类本身(而非实例)。最典型的应用是工厂方法——提供另一种创建实例的方式。

from datetime import datetime

class Person:
    planet = '地球'

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

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """根据出生年份创建实例的工厂方法"""
        age = datetime.now().year - birth_year
        return cls(name, age)

    @classmethod
    def change_planet(cls, new_planet):
        cls.planet = new_planet

p = Person.from_birth_year('小明', 2000)
print(p.age)                # 根据当前年份计算
Person.change_planet('火星')
print(Person.planet)        # 火星

4.2 静态方法

静态方法用 @staticmethod 修饰,没有 selfcls 参数,本质上就是一个定义在类命名空间内的普通函数。它通常用于实现与类相关但不需要访问类或实例内部状态的工具方法。

class Validator:
    @staticmethod
    def is_adult(age):
        return age >= 18

    @staticmethod
    def format_idcard(idcard):
        return idcard[:6] + '****' + idcard[-4:]

print(Validator.is_adult(20))                # True
print(Validator.format_idcard('110101199001011234'))
# 110101****1234

5. 继承:复用与扩展的基石

继承允许我们基于已有的类(父类/基类)构建新的类(子类/派生类),子类自动获得父类的属性和方法,并可以添加自己独有的成员,或重写父类的方法。

5.1 单继承

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

    def speak(self):
        print(f'{self.name} 说话了')

class Student(Person):
    def __init__(self, name, age, stu_id):
        # 调用父类初始化,复用父类属性设置逻辑
        super().__init__(name, age)
        self.stu_id = stu_id

    def study(self):
        print(f'{self.name} 正在学习,学号 {self.stu_id}')

s = Student('李华', 16, '2025001')
s.speak()    # 继承自 Person
s.study()    # Student 自己的方法

使用 super() 可以优雅地调用父类方法,特别是在多重继承时能保证正确的查找顺序。

5.2 方法重写(Override)

如果子类定义了与父类同名的方法,则父类方法会被覆盖(重写)。这为实现多态提供了基础。我们可以在重写的方法中通过 super() 调用父类版本,实现功能增强。

class Student(Person):
    def speak(self):
        super().speak()  # 先执行父类逻辑
        print(f'我的学号是 {self.stu_id}')

5.3 多重继承

Python 支持一个类同时继承多个父类:

class Worker:
    def __init__(self, company):
        self.company = company
    def work(self):
        print(f'在 {self.company} 工作')

class WorkingStudent(Person, Worker):
    def __init__(self, name, age, stu_id, company):
        Person.__init__(self, name, age)
        Worker.__init__(self, company)
        self.stu_id = stu_id

多重继承功能强大,但也会带来方法解析顺序(MRO)的复杂性。Python 使用 C3 线性化算法计算 MRO,通过 类名.__mro__ 可以查看属性/方法的查找顺序。

5.4 类型检查函数

  • isinstance(obj, Class):判断对象是否为指定类或其子类的实例。

  • issubclass(Sub, Super):判断一个类是否是另一个类的子类。

print(isinstance(s, Student))    # True
print(isinstance(s, Person))     # True
print(issubclass(Student, Person))  # True

6. 权限控制:约定胜于强制

Python 没有像 Java 或 C++ 那样的访问控制关键字,而是通过命名约定来区分属性的可见性:

命名风格

示例

含义

单下划线开头

_age

受保护属性,暗示“应该在类内部和子类中访问”

双下划线开头

__idcard

私有属性,通过名称改写机制限制外部直接访问

无下划线

name

公有属性,自由访问

6.1 名称改写机制

__ 开头的属性(不以 __ 结尾)会被 Python 自动改写为 _类名__属性名,这在一定程度上防止了外部意外访问,但并非绝对安全(依旧可以通过改写后的名称强制访问)。

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

p = Person('张三', '110101199001011234')
print(p.name)                # 张三
# print(p.__idcard)          # AttributeError
print(p._Person__idcard)     # 110101199001011234(强制访问,极不推荐)

6.2 getter 与 setter:受控的属性访问

通过 @property 装饰器,我们可以将方法伪装成属性,从而在不改变外部接口的前提下,为属性读写添加逻辑(如校验、脱敏)。

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

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value > 120:
            raise ValueError('年龄不合法')
        self._age = value

    @property
    def idcard(self):
        # 只返回脱敏后的信息
        return self.__idcard[:6] + '****' + self.__idcard[-4:]

    @idcard.setter
    def idcard(self, value):
        print('身份证号不可修改')

现在,我们可以像访问普通属性一样使用 p.agep.idcard,背后却执行了相应的方法。


7. 魔法方法:让对象“活起来”

以双下划线开头和结尾的方法被称为魔法方法(Magic Methods)或特殊方法。它们由 Python 解释器在特定场景下自动调用,让你的自定义对象能够像内置类型一样自然。

常用魔法方法一览:

方法

触发时机

__init__

实例创建时自动调用

__str__

print(obj)str(obj)

__repr__

交互环境直接显示对象或 repr(obj)

__len__

len(obj)

__lt__

obj1 < obj2

__gt__

obj1 > obj2

__eq__

obj1 == obj2

__getattr__

访问不存在的属性时

__add__

obj1 + obj2

实现这些方法后,我们的类就会变得更加 Pythonic:

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

    def __str__(self):
        return f'{self.name}({self.age}岁)'

    def __lt__(self, other):
        return self.age < other.age

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

p1 = Person('张三', 25)
p2 = Person('李四', 30)
print(p1)              # 张三(25岁)
print(p1 < p2)         # True
print(p1 == p2)        # False

此外,所有类的根基都是 object 类,它提供了一些默认的魔法方法实现。你可以重写它们来定制行为。


8. 多态 Python 风格:继承与鸭子类型

多态(Polymorphism)指的是同一个接口在不同对象上表现出不同的行为。Python 天然支持两种多态形式。

8.1 标准多态(基于继承)

定义父类接口,不同子类提供各自的实现,调用方只需面向父类编程。

class Animal:
    def speak(self):
        raise NotImplementedError

class Dog(Animal):
    def speak(self):
        return '汪汪汪'

class Cat(Animal):
    def speak(self):
        return '喵喵喵'

def animal_sound(animal: Animal):
    print(animal.speak())

animal_sound(Dog())   # 汪汪汪
animal_sound(Cat())   # 喵喵喵

8.2 鸭子类型(Duck Typing)

Python 是动态语言,它并不强制要求参数必须是某个特定类型。只要传入的对象拥有所需的方法,代码就能正常工作。这就是“鸭子类型”的核心思想:“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。”

class Car:
    def speak(self):
        return '嘀嘀嘀'

def make_sound(obj):
    print(obj.speak())

make_sound(Dog())   # 汪汪汪
make_sound(Car())   # 嘀嘀嘀

鸭子类型赋予了 Python 极大的灵活性,许多设计模式(如迭代器、上下文管理器)都依赖于此。但它也需要开发者更明确地管理接口约定,可以结合文档或抽象基类来增强可读性。


9. 抽象类:规范接口的契约

当我们希望强制子类实现某些方法时,可以使用 抽象基类(Abstract Base Class, ABC)。抽象类本身不能被实例化,其中用 @abstractmethod 装饰的方法必须在子类中重写。

from abc import ABC, abstractmethod

class MustRun(ABC):
    @abstractmethod
    def run(self):
        """子类必须实现 run 方法"""

class Person(MustRun):
    def run(self):
        print('人用双腿奔跑')

class Cheetah(MustRun):
    def run(self):
        print('猎豹高速冲刺')

# m = MustRun()  # TypeError: 不能实例化抽象类
p = Person()
p.run()          # 人用双腿奔跑

抽象类常用于框架设计、插件系统等场景,保证所有实现都遵循统一的接口规范。


10. 综合实战:学生成绩管理系统

为融合以上知识点,我们实现一个控制台版的学生成绩管理系统。系统允许添加学生、删除学生、查看所有学生、录入成绩,并自动计算平均分。设计上运用了继承、类属性、静态方法、魔法方法等。

from datetime import datetime

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

class Student(Person):
    count = 0   # 类属性,用于生成学号

    def __init__(self, name, age, gender):
        super().__init__(name, age, gender)
        Student.count += 1
        self.stu_id = f'{datetime.now().year}{Student.count:03d}'
        self.scores = {}

    def add_score(self, subject, score):
        self.scores[subject] = score

    def calc_avg(self):
        if not self.scores:
            return 0.0
        return sum(self.scores.values()) / len(self.scores)

    def __str__(self):
        return (f'{self.name}({self.age}-{self.gender}) '
                f'成绩:{self.scores} 平均分:{self.calc_avg():.1f}')

class Manager:
    def __init__(self):
        self.student_list = []

    def add_student(self):
        name = input('姓名:')
        age = int(input('年龄:'))
        gender = input('性别:')
        stu = Student(name, age, gender)
        self.student_list.append(stu)
        print(f'添加成功,学号:{stu.stu_id}')

    def delete_student(self):
        sid = input('请输入要删除的学号:')
        for stu in self.student_list:
            if stu.stu_id == sid:
                self.student_list.remove(stu)
                print('删除成功')
                return
        print('未找到该学生')

    def show_all(self):
        if not self.student_list:
            print('暂无学生')
        else:
            for stu in self.student_list:
                print(stu)

    def set_score(self):
        sid = input('请输入学号:')
        for stu in self.student_list:
            if stu.stu_id == sid:
                score_str = input('请输入成绩(格式:科目-分数,科目-分数):')
                items = score_str.replace(',', ',').split(',')
                for item in items:
                    subject, score = item.split('-')
                    stu.add_score(subject.strip(), float(score.strip()))
                print('成绩录入成功')
                return
        print('未找到该学生')

    def run(self):
        while True:
            print('\n**** 学生管理系统 ****')
            print('1.添加  2.删除  3.查看  4.录入成绩  5.退出')
            op = input('选择:')
            if op == '1':
                self.add_student()
            elif op == '2':
                self.delete_student()
            elif op == '3':
                self.show_all()
            elif op == '4':
                self.set_score()
            elif op == '5':
                print('再见!')
                break
            else:
                print('无效输入')

if __name__ == '__main__':
    mgr = Manager()
    mgr.run()

这个系统虽小巧,但完整展示了面向对象设计的核心骨架:数据封装(Student 封装个人信息与成绩)、继承(Student 继承 Person)、类属性(count 计数器)、实例方法(add_score、calc_avg)、魔法方法__str__ 定制打印格式)。你可以基于此继续扩展出更多功能,如排序、文件持久化等。


11. 小结与选型建议

Python 的面向对象体系兼具经典面向对象语言的严谨和动态语言的轻便。在实际开发中,你并不需要为了 OOP 而 OOP,而是应该在下列场景优先考虑面向对象设计:

  • 需要创建一批具备相似属性和行为的实体时(如用户、商品、订单)。

  • 存在清晰的“is-a”继承关系时(如学生是人,猫是动物)。

  • 希望通过多态统一操作不同类型的对象时(如不同支付方式均实现 pay 方法)。

  • 需要封装复杂状态,并对外提供可控接口时。

同时,结合 Python 特有的鸭子类型抽象基类,你可以在保持灵活性的同时,获得清晰的接口契约。

无论你是在构建小型脚本还是大型系统,理解并善用这些面向对象的核心概念,都将让你的代码更具可读性、可维护性和可扩展性。希望这篇深度指南能成为你 Python OOP 旅程上的可靠参考。

06数据容器 2026-04-24

评论区