目录

前言

第一章 面向对象核心概念

1.1 什么是面向对象

1.2 类与对象的关系

1.3 面向对象与面向过程的区别

第二章 类与对象的基础操作

2.1 类的定义

2.2 对象的创建与使用

2.2.1 创建对象

2.2.2 访问对象的属性

2.2.3 调用对象的方法

2.2.4 修改和删除对象的属性

2.3 类属性与实例属性

2.3.1 实例属性

2.3.2 类属性

2.4 类方法与静态方法

2.4.1 类方法

2.4.2 静态方法

2.4.3 三种方法的对比

第三章 面向对象三大特性

3.1 封装(Encapsulation)

3.1.1 什么是封装

3.1.2 私有属性与私有方法

3.1.3 封装的好处

3.2 继承(Inheritance)

3.2.1 什么是继承

3.2.2 继承的语法

3.2.3 多继承

3.2.4 继承的好处

3.3 多态(Polymorphism)

3.3.1 什么是多态

3.3.2 多态的示例

3.3.3 多态的好处

第四章 面向对象进阶技巧

4.1 魔术方法(特殊方法)

4.1.1 常用魔术方法(重点掌握)

4.2 @property装饰器(核心进阶)

4.2.1 @property的基本使用

4.2.2 可修改的@property(搭配@属性名.setter)

4.2.3 可删除的@property(搭配@属性名.deleter)

4.3 类的私有化进阶(补充)

4.3.1 单下划线(_)的半私有属性

4.3.2 私有化的注意事项

4.4 抽象类(abc模块)

4.5 Mixin混入(多继承的优化方案)

4.6 进阶技巧总结


前言

面向对象程序设计(Object-Oriented Programming,简称OOP)是一种主流的编程范式,其核心思想是将程序中的数据和操作数据的方法封装成“对象”,通过对象之间的交互完成程序功能。与面向过程(注重“步骤”)不同,面向对象更注重“事物”的描述与交互,能显著提升代码的复用性、可读性和可维护性。

Python是一门完全支持面向对象的编程语言,其面向对象特性简洁易懂,无需复杂的语法约束,既能满足入门学习,也能支撑大型项目开发。本教程将从基础概念入手,逐步深入讲解类与对象、属性与方法、面向对象三大特性(封装、继承、多态),以及进阶技巧,搭配大量实例代码,帮助初学者快速掌握Python面向对象编程。

适用人群:具备Python基础语法(变量、函数、条件判断、循环等)的初学者;想要从面向过程转向面向对象编程的开发者。

学习目标:掌握类与对象的定义和使用;理解封装、继承、多态的核心思想;能独立编写面向对象程序,解决实际问题。

第一章 面向对象核心概念

1.1 什么是面向对象

生活中万物皆可视为“对象”,比如一辆汽车、一个人、一本书。每个对象都有两个核心特征:属性(描述对象的特征,如汽车的颜色、品牌、速度;人的姓名、年龄、身高)和方法(对象能执行的操作,如汽车的启动、刹车;人的吃饭、睡觉、工作)。

面向对象编程就是模拟现实世界,将程序中的数据(属性)和操作数据的函数(方法)绑定在一起,形成“对象”,通过对象之间的调用实现功能。例如,用面向对象编写一个“学生管理系统”,可以将每个学生视为一个对象,学生的姓名、学号、成绩是属性,查询成绩、修改成绩是方法。

1.2 类与对象的关系

在Python中,类(Class)是对象的“模板”,是对某一类事物的抽象描述;对象(Object)是类的“实例”,是根据模板创建出的具体事物。

举个例子:“人类”是一个类,它描述了所有人类的共同属性(姓名、年龄)和共同方法(吃饭、走路);而“张三”“李四”就是“人类”这个类的对象,他们有具体的姓名(张三)、年龄(20),也能执行吃饭、走路等方法。

核心关系:类是抽象的,对象是具体的;一个类可以创建多个对象,每个对象都拥有类中定义的属性和方法,但属性值可以不同。

1.3 面向对象与面向过程的区别

为了更清晰理解面向对象,我们对比面向过程(Procedure-Oriented Programming,简称POP)的编程思路:

对比维度

面向过程

面向对象

核心思想

注重“步骤”,将程序拆解为多个步骤,按顺序执行

注重“对象”,将程序拆解为多个对象,通过对象交互完成功能

代码组织方式

以函数为核心,数据和函数分离

以类/对象为核心,数据(属性)和函数(方法)封装在一起

复用性

较低,函数复用需重复调用,修改不便

较高,通过继承、封装实现代码复用,修改更灵活

可读性

适合简单程序,复杂程序步骤繁多,可读性差

模拟现实世界,结构清晰,可读性强

适用场景

简单脚本、小型程序(如计算一个数的阶乘)

大型项目、复杂程序(如网站、APP、管理系统)

示例:用两种方式实现“计算长方形面积”

1. 面向过程

# 定义函数(步骤)
def calculate_area(length, width):
    return length * width

# 执行步骤
length = 5
width = 3
area = calculate_area(length, width)
print(f"长方形面积:{area}")

2. 面向对象

# 定义类(模板)
class Rectangle:
    # 定义属性(长、宽)
    def __init__(self, length, width):
        self.length = length
        self.width = width
    
    # 定义方法(计算面积)
    def calculate_area(self):
        return self.length * self.width

# 创建对象(具体长方形)
rect = Rectangle(5, 3)
# 调用对象的方法
area = rect.calculate_area()
print(f"长方形面积:{area}")

第二章 类与对象的基础操作

2.1 类的定义

在Python中,使用class关键字定义类,语法格式如下:

class 类名(父类名):
    # 类的文档字符串(可选,用于说明类的功能)
    """类的描述信息"""
    
    # 构造方法(初始化对象,可选但常用)
    def __init__(self, 参数1, 参数2, ...):
        # 定义实例属性
        self.属性1 = 参数1
        self.属性2 = 参数2
    
    # 定义其他方法(可选)
    def 方法名(self, 参数1, ...):
        # 方法体(实现具体功能)
        pass

说明:

  • 类名:遵循“大驼峰命名法”(每个单词首字母大写,无下划线),如Rectangle、Student,避免使用Python关键字(如class、def)。

  • 父类名:可选,若不指定父类,默认继承自Python的基类object(所有类的最终父类),可省略不写,即class 类名: 等价于 class 类名(object):。

  • __init__方法:称为“构造方法”或“初始化方法”,当创建对象时自动调用,用于初始化对象的属性。self是必选参数,且必须位于第一个位置,代表当前对象本身(类似其他语言的this),无需手动传递。

  • self:在类的方法中,self用于访问当前对象的属性和方法,所有实例方法的第一个参数必须是self。

示例:定义一个“学生”类(Student)

class Student:
    """学生类,用于描述学生的基本信息和行为"""
    
    # 构造方法,初始化学生的姓名、学号、成绩
    def __init__(self, name, student_id, score):
        # 实例属性:每个对象独有的属性
        self.name = name          # 姓名
        self.student_id = student_id  # 学号
        self.score = score        # 成绩
    
    # 实例方法:查询学生信息
    def show_info(self):
        print(f"学号:{self.student_id},姓名:{self.name},成绩:{self.score}")
    
    # 实例方法:修改学生成绩
    def update_score(self, new_score):
        if 0 <= new_score <= 100:
            self.score = new_score
            print(f"成绩修改成功,新成绩:{self.score}")
        else:
            print("成绩无效,需在0-100之间")

2.2 对象的创建与使用

创建对象(实例化对象)的语法非常简单,直接使用“类名(参数)”即可,参数对应构造方法__init__中的参数(除self外)。

2.2.1 创建对象

# 创建第一个学生对象(调用__init__方法,传递参数)
stu1 = Student("张三", "2024001", 90)
# 创建第二个学生对象
stu2 = Student("李四", "2024002", 85)

说明:创建对象时,会自动调用__init__方法,将传递的参数赋值给对象的属性,stu1和stu2是两个独立的对象,拥有各自的属性值。

2.2.2 访问对象的属性

访问对象的属性有两种方式:

  1. 对象名.属性名(最常用)

  2. getattr(对象名, "属性名")(动态访问,适用于属性名不确定的场景)

# 方式1:对象名.属性名
print(stu1.name)        # 输出:张三
print(stu2.student_id)  # 输出:2024002

# 方式2:getattr()
print(getattr(stu1, "score"))  # 输出:90
print(getattr(stu2, "name"))   # 输出:李四

# 若属性不存在,可设置默认值,避免报错
print(getattr(stu1, "age", 18))  # 输出:18(stu1没有age属性,返回默认值18)

2.2.3 调用对象的方法

调用对象的方法格式:对象名.方法名(参数),无需传递self参数(Python会自动传递当前对象)。

# 调用show_info方法,查询学生信息
stu1.show_info()  # 输出:学号:2024001,姓名:张三,成绩:90
stu2.show_info()  # 输出:学号:2024002,姓名:李四,成绩:85

# 调用update_score方法,修改成绩
stu1.update_score(95)  # 输出:成绩修改成功,新成绩:95
stu2.update_score(105) # 输出:成绩无效,需在0-100之间

# 再次查询修改后的信息
stu1.show_info()  # 输出:学号:2024001,姓名:张三,成绩:95

2.2.4 修改和删除对象的属性

# 修改属性值(直接赋值)
stu1.name = "张三三"
stu1.show_info()  # 输出:学号:2024001,姓名:张三三,成绩:95

# 删除属性(del关键字)
del stu1.score
# print(stu1.score)  # 报错:AttributeError(属性已删除)

# 删除对象(del关键字)
del stu2
# stu2.show_info()  # 报错:NameError(对象已删除)

2.3 类属性与实例属性

在Python中,属性分为两种:实例属性类属性,两者的作用域和使用场景不同。

2.3.1 实例属性

实例属性是每个对象独有的属性,定义在__init__方法中,通过self赋值,不同对象的实例属性值可以不同。

示例:Student类中的name、student_id、score都是实例属性,stu1和stu2的name不同,score也可以不同。

2.3.2 类属性

类属性是类的所有对象共享的属性,定义在类的内部、所有方法的外部,无需通过self赋值,所有对象访问类属性时,获取的是同一个值。

语法:

class 类名:
    # 类属性(所有对象共享)
    类属性名 = 属性值
    
    def __init__(self):
        # 实例属性(每个对象独有)
        self.实例属性名 = 属性值

示例:给Student类添加“学校”类属性(所有学生的学校相同)

class Student:
    # 类属性:所有学生共享的学校名称
    school = "北京大学"
    
    def __init__(self, name, student_id, score):
        # 实例属性
        self.name = name
        self.student_id = student_id
        self.score = score
    
    def show_info(self):
        # 访问类属性:类名.类属性名 或 self.类属性名
        print(f"学校:{Student.school},学号:{self.student_id},姓名:{self.name},成绩:{self.score}")

# 创建对象
stu1 = Student("张三", "2024001", 90)
stu2 = Student("李四", "2024002", 85)

# 访问类属性
print(Student.school)  # 输出:北京大学(通过类名访问,推荐)
print(stu1.school)     # 输出:北京大学(通过对象访问)
print(stu2.school)     # 输出:北京大学

# 修改类属性(只能通过类名修改,修改后所有对象的类属性值都会变)
Student.school = "清华大学"
stu1.show_info()  # 输出:学校:清华大学,学号:2024001,姓名:张三,成绩:90
stu2.show_info()  # 输出:学校:清华大学,学号:2024002,姓名:李四,成绩:85

# 注意:通过对象修改类属性,其实是给对象添加了一个同名的实例属性,不会影响类属性和其他对象
stu1.school = "复旦大学"
print(Student.school)  # 输出:清华大学(类属性未变)
print(stu1.school)     # 输出:复旦大学(stu1的实例属性)
print(stu2.school)     # 输出:清华大学(其他对象的类属性未变)

核心区别:

  • 实例属性:对象独有,修改一个对象的实例属性,不影响其他对象。

  • 类属性:所有对象共享,修改类属性(通过类名),所有对象的类属性值都会改变。

2.4 类方法与静态方法

除了实例方法(第一个参数是self),Python中还有两种方法:类方法静态方法,两者都定义在类中,但作用和调用方式不同。

2.4.1 类方法

类方法是属于类的方法,而非对象,使用@classmethod装饰器定义,第一个参数是cls(代表当前类,类似self,但self是对象,cls是类)。

作用:通常用于操作类属性,或创建类的实例,无需依赖具体对象。

语法:

class 类名:
    类属性 = 属性值
    
    @classmethod
    def 类方法名(cls, 参数1, ...):
        # 操作类属性(cls.类属性名)
        pass

示例:给Student类添加类方法,修改学校名称

class Student:
    school = "北京大学"
    
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        self.score = score
    
    # 实例方法
    def show_info(self):
        print(f"学校:{self.school},姓名:{self.name},成绩:{self.score}")
    
    # 类方法(修改类属性)
    @classmethod
    def update_school(cls, new_school):
        cls.school = new_school
        print(f"学校名称已修改为:{cls.school}")

# 调用类方法:类名.类方法名(参数)(无需创建对象)
Student.update_school("清华大学")

# 创建对象,验证类属性修改
stu1 = Student("张三", "2024001", 90)
stu1.show_info()  # 输出:学校:清华大学,姓名:张三,成绩:90

2.4.2 静态方法

静态方法是与类和对象都无关的方法,使用@staticmethod装饰器定义,没有默认参数(无需self或cls)。

作用:通常用于实现一些通用功能,这些功能与类的属性、对象的属性无关,只是逻辑上属于该类。

语法:

class 类名:
    @staticmethod
    def 静态方法名(参数1, ...):
        # 通用功能实现
        pass

示例:给Student类添加静态方法,判断成绩是否合格

class Student:
    school = "北京大学"
    
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        self.score = score
    
    # 静态方法:判断成绩是否合格(与类、对象无关,仅处理参数)
    @staticmethod
    def is_pass(score):
        return score >= 60

# 调用静态方法:类名.静态方法名(参数)(无需创建对象)
print(Student.is_pass(90))  # 输出:True
print(Student.is_pass(55))  # 输出:False

# 也可以通过对象调用(不推荐,静态方法与对象无关)
stu1 = Student("张三", "2024001", 90)
print(stu1.is_pass(stu1.score))  # 输出:True

2.4.3 三种方法的对比

方法类型

装饰器

第一个参数

作用

调用方式

实例方法

self(当前对象)

操作实例属性,实现对象的具体行为

对象名.方法名()

类方法

@classmethod

cls(当前类)

操作类属性,创建类实例

类名.方法名()(推荐)、对象名.方法名()

静态方法

@staticmethod

通用功能,与类、对象无关

类名.方法名()(推荐)、对象名.方法名()

第三章 面向对象三大特性

面向对象的三大核心特性是:封装继承多态,它们是实现代码复用、灵活扩展的关键,也是面向对象编程的灵魂。

3.1 封装(Encapsulation)

3.1.1 什么是封装

封装的核心思想是:将对象的属性和方法隐藏起来,只对外提供有限的访问接口,禁止外部直接访问对象的内部数据,从而保护数据的安全性,避免误操作。

举个例子:手机的内部零件(CPU、电池、主板)是“隐藏”的,用户不需要知道内部结构,只需要通过屏幕、按键(接口)操作手机即可,这就是封装。

在Python中,封装的实现方式:通过“私有属性”和“私有方法”隐藏内部细节,再通过“公有方法”提供访问接口。

3.1.2 私有属性与私有方法

Python中,通过在属性名或方法名前添加两个下划线(__),将其定义为私有属性/私有方法,私有属性/方法只能在类的内部访问,外部无法直接访问。

语法:

class 类名:
    def __init__(self):
        # 私有属性(两个下划线开头)
        self.__私有属性名 = 属性值
    
    # 私有方法(两个下划线开头)
    def __私有方法名(self):
        # 方法体
        pass
    
    # 公有方法(提供访问接口)
    def 公有方法名(self):
        # 在公有方法中访问私有属性和私有方法
        print(self.__私有属性名)
        self.__私有方法名()

示例:封装Student类的成绩属性(禁止外部直接修改成绩,只能通过指定方法修改)

class Student:
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        # 私有属性:成绩,外部无法直接访问
        self.__score = score
    
    # 私有方法:验证成绩是否有效(内部使用)
    def __is_valid_score(self, score):
        return 0 <= score <= 100
    
    # 公有方法:查询成绩(提供访问接口)
    def get_score(self):
        return self.__score
    
    # 公有方法:修改成绩(提供修改接口,包含验证逻辑)
    def set_score(self, new_score):
        if self.__is_valid_score(new_score):
            self.__score = new_score
            print(f"成绩修改成功,新成绩:{self.__score}")
        else:
            print("成绩无效,需在0-100之间")

# 创建对象
stu1 = Student("张三", "2024001", 90)

# 尝试直接访问私有属性(报错)
# print(stu1.__score)  # AttributeError: 'Student' object has no attribute '__score'

# 尝试直接调用私有方法(报错)
# stu1.__is_valid_score(95)  # AttributeError: 'Student' object has no attribute '__is_valid_score'

# 通过公有方法访问和修改私有属性
print(stu1.get_score())  # 输出:90
stu1.set_score(95)       # 输出:成绩修改成功,新成绩:95
stu1.set_score(105)      # 输出:成绩无效,需在0-100之间

说明:Python的私有属性/方法并非真正的“私有”,而是通过“名称修饰”(name mangling)实现的,外部可以通过“对象名._类名__私有属性名”的方式访问,但不推荐这样做,违背封装的初衷。

# 不推荐:通过名称修饰访问私有属性
print(stu1._Student__score)  # 输出:95

3.1.3 封装的好处

  • 数据安全:防止外部误操作修改对象的内部数据(如成绩不能超过100)。

  • 代码可维护性:内部实现细节可以自由修改,只要对外的接口不变,就不会影响外部代码。

  • 代码简洁:外部只需调用接口,无需关注内部实现逻辑。

3.2 继承(Inheritance)

3.2.1 什么是继承

继承的核心思想是:一个类(子类)可以继承另一个类(父类)的属性和方法,子类无需重复定义父类已有的内容,只需补充自己独有的属性和方法,实现代码复用。

举个例子:“学生”和“老师”都是“人”,“人”类有姓名、年龄、吃饭、走路等属性和方法,“学生”类可以继承“人”类的所有内容,再添加学号、成绩等独有属性,以及上课、考试等独有方法;“老师”类可以继承“人”类,再添加工号、职称等独有属性,以及讲课、批改作业等独有方法。

相关概念:

  • 父类(基类):被继承的类,如“人”类。

  • 子类(派生类):继承父类的类,如“学生”“老师”类。

  • 单继承:子类只继承一个父类(Python支持单继承)。

  • 多继承:子类继承多个父类(Python也支持多继承,语法:class 子类名(父类1, 父类2, ...):)。

3.2.2 继承的语法

# 定义父类
class 父类名:
    父类属性 = 属性值
    
    def 父类方法(self):
        pass

# 定义子类,继承父类
class 子类名(父类名):
    # 子类独有的属性
    子类属性 = 属性值
    
    # 子类独有的方法
    def 子类方法(self):
        pass
    
    # (可选)重写父类方法
    def 父类方法(self):
        pass

示例1:单继承(Student类继承Person类)

# 父类:Person(人)
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    # 父类方法:吃饭
    def eat(self):
        print(f"{self.name}在吃饭")
    
    # 父类方法:走路
    def walk(self):
        print(f"{self.name}在走路")

# 子类:Student(学生),继承Person类
class Student(Person):
    # 子类独有的构造方法(需要调用父类的构造方法,初始化父类的属性)
    def __init__(self, name, age, student_id, score):
        # 调用父类的构造方法:两种方式
        # 方式1:父类名.__init__(self, 参数)
        Person.__init__(self, name, age)
        # 方式2:super().__init__(参数)(推荐,无需写父类名,更灵活)
        # super().__init__(name, age)
        
        # 子类独有的属性
        self.student_id = student_id
        self.score = score
    
    # 子类独有的方法:上课
    def study(self):
        print(f"{self.name}(学号:{self.student_id})在上课")
    
    # 重写父类方法:修改父类eat方法的功能
    def eat(self):
        print(f"{self.name}在学校食堂吃饭")

# 创建子类对象
stu1 = Student("张三", 20, "2024001", 90)

# 调用父类的属性和方法(继承而来)
print(stu1.name)    # 输出:张三(父类属性)
print(stu1.age)     # 输出:20(父类属性)
stu1.walk()         # 输出:张三在走路(父类方法)

# 调用子类独有的属性和方法
print(stu1.student_id)  # 输出:2024001(子类属性)
stu1.study()            # 输出:张三(学号:2024001)在上课(子类方法)

# 调用重写后的父类方法
stu1.eat()           # 输出:张三在学校食堂吃饭(重写后的方法)

说明:

  • 子类的构造方法中,必须调用父类的构造方法,否则父类的属性无法初始化(会报错)。

  • super()函数:用于调用父类的方法,无需指定父类名,当父类名称修改时,无需修改子类代码,更推荐使用。

  • 方法重写:子类可以定义与父类同名的方法,从而覆盖父类的方法,实现子类特有的功能(如上述Student类重写了eat方法)。

3.2.3 多继承

Python支持多继承,即一个子类可以继承多个父类,子类会拥有所有父类的属性和方法。

语法:class 子类名(父类1, 父类2, ...):

示例:定义一个“研究生”类(Graduate),继承Student类和Teacher类(假设Teacher类有讲课方法)

# 父类1:Person
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def eat(self):
        print(f"{self.name}在吃饭")

# 父类2:Student
class Student(Person):
    def __init__(self, name, age, student_id):
        super().__init__(name, age)
        self.student_id = student_id
    
    def study(self):
        print(f"{self.name}在学习")

# 父类3:Teacher
class Teacher(Person):
    def __init__(self, name, age, teacher_id):
        super().__init__(name, age)
        self.teacher_id = teacher_id
    
    def teach(self):
        print(f"{self.name}在讲课")

# 子类:Graduate(研究生),继承Student和Teacher
class Graduate(Student, Teacher):
    def __init__(self, name, age, student_id, teacher_id, research_topic):
        # 调用两个父类的构造方法
        Student.__init__(self, name, age, student_id)
        Teacher.__init__(self, name, age, teacher_id)
        # 子类独有的属性:研究方向
        self.research_topic = research_topic
    
    # 子类独有的方法:做研究
    def do_research(self):
        print(f"{self.name}(学号:{self.student_id},工号:{self.teacher_id})在研究{self.research_topic}")

# 创建子类对象
grad1 = Graduate("李四", 25, "2024002", "T2024001", "人工智能")

# 调用各个父类的方法
grad1.eat()       # 输出:李四在吃饭(Person类方法)
grad1.study()     # 输出:李四在学习(Student类方法)
grad1.teach()     # 输出:李四在讲课(Teacher类方法)

# 调用子类独有的方法
grad1.do_research()  # 输出:李四(学号:2024002,工号:T2024001)在研究人工智能

注意:多继承可能会出现“菱形继承”(多个父类继承自同一个基类),导致方法调用冲突,Python通过“方法解析顺序(MRO)”解决这个问题,优先调用继承顺序中靠前的父类方法。

3.2.4 继承的好处

  • 代码复用:子类无需重复定义父类已有的属性和方法,减少代码冗余。

  • 代码扩展:子类可以在父类的基础上,添加独有的属性和方法,实现功能扩展。

  • 代码层次清晰:通过继承构建类的层次结构,模拟现实世界的关系(如人→学生→研究生)。

3.3 多态(Polymorphism)

3.3.1 什么是多态

多态的核心思想是:同一方法调用,由于对象不同,会产生不同的执行结果。换句话说,子类继承父类的方法后,重写该方法,当调用该方法时,传入不同的子类对象,会执行不同的逻辑。

多态的前提:

  1. 存在继承关系(子类继承父类)。

  2. 子类重写父类的方法。

举个例子:“动物”类有一个“叫”的方法,“猫”类和“狗”类继承“动物”类,并重写“叫”的方法,当调用“叫”的方法时,猫会“喵喵叫”,狗会“汪汪叫”,这就是多态。

3.3.2 多态的示例

# 父类:Animal(动物)
class Animal:
    def __init__(self, name):
        self.name = name
    
    # 父类方法:叫(未具体实现,由子类重写)
    def bark(self):
        pass

# 子类1:Cat(猫),重写bark方法
class Cat(Animal):
    def bark(self):
        print(f"{self.name}喵喵叫")

# 子类2:Dog(狗),重写bark方法
class Dog(Animal):
    def bark(self):
        print(f"{self.name}汪汪叫")

# 子类3:Bird(鸟),重写bark方法
class Bird(Animal):
    def bark(self):
        print(f"{self.name}叽叽叫")

# 定义一个通用函数,调用动物的bark方法(体现多态)
def animal_bark(animal):
    # 无论传入的是Cat、Dog还是Bird对象,都调用bark方法
    animal.bark()

# 创建不同的动物对象
cat = Cat("小花")
dog = Dog("旺财")
bird = Bird("小麻雀")

# 调用通用函数,传入不同对象,执行不同逻辑
animal_bark(cat)   # 输出:小花喵喵叫
animal_bark(dog)   # 输出:旺财汪汪叫
animal_bark(bird)  # 输出:小麻雀叽叽叫

说明:

  • animal_bark函数接收一个Animal类型的参数,但实际上可以传入任何继承自Animal的子类对象(Cat、Dog、Bird)。

  • 调用animal.bark()时,会根据传入的对象类型,执行对应的子类重写后的bark方法,实现“同一调用,不同结果”。

3.3.3 多态的好处

  • 代码灵活性:无需修改通用函数(如animal_bark),只需添加新的子类(如Fish),并重写bark方法,即可实现新的功能。

  • 代码可扩展性:新增子类时,不影响原有代码的逻辑,符合“开放-封闭原则”(对扩展开放,对修改封闭)。

  • 代码简洁:通用函数无需判断对象类型,直接调用方法即可,减少代码冗余。

第四章 面向对象进阶技巧

4.1 魔术方法(特殊方法)

Python中,以双下划线开头和结尾(__方法名__)的方法称为“魔术方法”(或特殊方法),这些方法无需手动调用,在特定场景下会自动触发,能极大简化代码,提升类的灵活性。对于初学者而言,重点掌握常用魔术方法即可,无需记忆所有方法。

4.1.1 常用魔术方法(重点掌握)

#### 1. 初始化与字符串表示魔术方法

这类方法用于初始化对象、定义对象的字符串显示形式,是最常用的魔术方法,前文已接触过__init__,此处补充完整并新增相关方法。

class Student:
    def __init__(self, name, student_id, score):
        # __init__:初始化魔术方法,创建对象时自动触发
        self.name = name
        self.student_id = student_id
        self.score = score
    
    def __str__(self):
        # __str__:打印对象时自动触发,返回用户友好的字符串(面向用户)
        return f"学生[姓名:{self.name},学号:{self.student_id},成绩:{self.score}]"
    
    def __repr__(self):
        # __repr__:在交互模式/调试时自动触发,返回对象的详细信息(面向开发者)
        return f"Student(name='{self.name}', student_id='{self.student_id}', score={self.score})"

# 测试魔术方法
stu = Student("张三", "2024001", 90)
print(stu)  # 触发__str__,输出:学生[姓名:张三,学号:2024001,成绩:90]
stu  # 交互模式下触发__repr__,输出:Student(name='张三', student_id='2024001', score=90)

说明:__str__和__repr__的核心区别的是使用场景,初学者可记住:print()调用__str__,调试时调用__repr__,若只定义一个,建议定义__repr__(__str__默认会调用__repr__)。

#### 2. 算术运算魔术方法

用于实现对象之间的算术运算(如加减乘除),让自定义对象支持类似数字的运算,示例如下:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # 加法运算:对象1 + 对象2 时触发
    def __add__(self, other):
        # other表示另一个对象,需判断类型,避免报错(初学者可简化)
        if isinstance(other, Point):
            return Point(self.x + other.x, self.y + other.y)
        return "无法相加,需传入Point对象"
    
    # 减法运算:对象1 - 对象2 时触发
    def __sub__(self, other):
        if isinstance(other, Point):
            return Point(self.x - other.x, self.y - other.y)
        return "无法相减,需传入Point对象"
    
    def __str__(self):
        return f"Point({self.x}, {self.y})"

# 测试算术魔术方法
p1 = Point(3, 5)
p2 = Point(2, 4)
p3 = p1 + p2  # 触发__add__方法
p4 = p1 - p2  # 触发__sub__方法
print(p3)  # 输出:Point(5, 9)
print(p4)  # 输出:Point(1, 1)

常用算术魔术方法:__add__(+)、__sub__(-)、__mul__(*)、__div__(/),初学者无需全部掌握,按需学习即可。

#### 3. 其他常用魔术方法

  • __len__:调用len(对象)时触发,用于返回对象的“长度”(如自定义列表类)。

  • __del__:删除对象时自动触发(垃圾回收时),用于释放资源(初学者很少用到)。

  • __eq__:判断两个对象是否相等(==)时触发,默认比较内存地址,可自定义比较规则。

class MyList:
    def __init__(self, data):
        self.data = data
    
    def __len__(self):
        return len(self.data)

my_list = MyList([1, 2, 3, 4])
print(len(my_list))  # 触发__len__,输出:4

4.2 @property装饰器(核心进阶)

前文讲解封装时,我们通过get_score()、set_score()等公有方法访问和修改私有属性,这种方式略显繁琐。@property装饰器可以将类的方法“伪装”成属性,实现“属性调用”的简洁语法,同时保留方法的逻辑(如数据验证),是Python面向对象中简化封装的核心技巧。

4.2.1 @property的基本使用

语法:用@property装饰一个方法,该方法无需参数(除self),返回值即为“属性值”,调用时直接用“对象.方法名”(无需加括号)。

class Student:
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        # 私有属性,用于存储成绩,外部无法直接访问
        self.__score = score
    
    # 用@property装饰,将get_score方法伪装成属性
    @property
    def score(self):
        # 可添加额外逻辑(如日志、格式处理)
        print("正在查询成绩...")
        return self.__score

# 测试@property
stu = Student("张三", "2024001", 90)
# 调用时无需加括号,像访问普通属性一样
print(stu.score)  # 输出:正在查询成绩...  90
# 无法直接修改(相当于只读属性)
# stu.score = 95  # 报错:AttributeError: can't set attribute

核心作用:将方法伪装成属性,简化调用语法,同时隐藏内部实现(如私有属性__score),实现“只读”属性(默认情况下,无法直接赋值修改)。

4.2.2 可修改的@property(搭配@属性名.setter)

若需要修改@property装饰的“属性”,可搭配@属性名.setter装饰器,定义一个同名方法,用于处理赋值逻辑(如数据验证),实现“可读可写”的封装。

class Student:
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        self.__score = score
    
    # 只读/查询:@property装饰
    @property
    def score(self):
        print("正在查询成绩...")
        return self.__score
    
    # 可写/修改:@score.setter装饰(名称必须与@property装饰的方法一致)
    @score.setter
    def score(self, new_score):
        # 添加数据验证逻辑(核心优势)
        if 0 <= new_score <= 100:
            self.__score = new_score
            print(f"成绩修改成功,新成绩:{self.__score}")
        else:
            print("成绩无效,需在0-100之间")

# 测试可读可写
stu = Student("张三", "2024001", 90)
print(stu.score)  # 触发@property,输出:正在查询成绩...  90
stu.score = 95    # 触发@score.setter,输出:成绩修改成功,新成绩:95
stu.score = 105   # 触发@score.setter,输出:成绩无效,需在0-100之间
print(stu.score)  # 输出:正在查询成绩...  95

4.2.3 可删除的@property(搭配@属性名.deleter)

若需要支持删除@property装饰的“属性”,可搭配@属性名.deleter装饰器,定义同名方法,处理删除逻辑(初学者较少用到)。

class Student:
    def __init__(self, name, student_id, score):
        self.name = name
        self.student_id = student_id
        self.__score = score
    
    @property
    def score(self):
        return self.__score
    
    @score.setter
    def score(self, new_score):
        if 0 <= new_score <= 100:
            self.__score = new_score
        else:
            print("成绩无效")
    
    @score.deleter
    def score(self):
        print("删除成绩属性...")
        del self.__score

# 测试删除
stu = Student("张三", "2024001", 90)
del stu.score  # 触发@score.deleter,输出:删除成绩属性...
# print(stu.score)  # 报错:AttributeError(属性已删除)

总结:@property的核心价值是“简化封装”,无需手动调用get/set方法,既能保护私有属性,又能让语法更简洁,是Python面向对象进阶的必学技巧。

4.3 类的私有化进阶(补充)

前文讲解了双下划线(__)的私有属性/方法,此处补充单下划线(_)的“半私有”用法,以及私有化的注意事项,贴合初学者避坑需求。

4.3.1 单下划线(_)的半私有属性

Python中,单下划线开头的属性/方法(如_name),称为“约定俗成的私有属性”,并非真正的私有,外部可以访问,但开发者约定:这类属性/方法仅供内部使用,外部不要随意访问和修改。

class Student:
    def __init__(self, name, age):
        self.name = name  # 公开属性
        self._age = age   # 半私有属性(约定俗成,内部使用)
    
    def _show_age(self):  # 半私有方法
        print(f"年龄:{self._age}")

stu = Student("张三", 20)
# 外部可以访问,但不推荐
print(stu._age)        # 输出:20
stu._show_age()        # 输出:年龄:20

区别:双下划线(__)会触发“名称修饰”,外部无法直接访问;单下划线(_)仅为约定,无语法限制,适合团队开发中规范代码。

4.3.2 私有化的注意事项

  • 不要强行访问双下划线的私有属性(如_stu__score),违背封装初衷,且会导致代码可读性、可维护性下降。

  • 单下划线的半私有属性,仅用于团队内部约定,若为公开项目,建议用双下划线+@property实现真正的封装。

4.4 抽象类(abc模块)

抽象类是“不能实例化的类”,用于定义子类的“接口规范”——抽象类中定义抽象方法(只有方法名,没有方法体),子类必须重写这些抽象方法,否则无法实例化。抽象类的核心作用是统一子类的方法规范,适合大型项目开发。

Python中,抽象类需通过abc模块实现(abc:Abstract Base Class,抽象基类),初学者重点掌握“定义抽象类”和“子类实现”即可。

from abc import ABCMeta, abstractmethod

# 定义抽象类:继承ABCMeta,用@abstractmethod装饰抽象方法
class Animal(metaclass=ABCMeta):
    @abstractmethod
    def bark(self):
        # 抽象方法:只有方法名,没有方法体,子类必须重写
        pass
    
    def eat(self):
        # 普通方法:抽象类中可以有普通方法,子类可直接继承
        print("动物在吃饭")

# 子类1:Cat,重写抽象方法bark
class Cat(Animal):
    def bark(self):
        print("喵喵叫")

# 子类2:Dog,重写抽象方法bark
class Dog(Animal):
    def bark(self):
        print("汪汪叫")

# 测试抽象类
# animal = Animal()  # 报错:Cannot instantiate abstract class Animal with abstract method bark(无法实例化抽象类)
cat = Cat()
dog = Dog()
cat.bark()  # 输出:喵喵叫
dog.bark()  # 输出:汪汪叫
cat.eat()   # 输出:动物在吃饭(继承抽象类的普通方法)

注意:抽象类的核心是“规范”,确保所有子类都实现相同的方法,避免子类方法名不一致导致的错误,适合多子类开发场景。

4.5 Mixin混入(多继承的优化方案)

前文讲解多继承时,提到“菱形继承”可能导致方法冲突,而Mixin混入是Python中优化多继承的常用技巧——Mixin类是“只提供方法,不提供属性”的类,专门用于被其他类继承,实现功能复用,不参与实例化。

核心特点:Mixin类名通常以Mixin结尾,只定义方法,不定义__init__构造方法(避免与子类构造方法冲突),子类继承时,将Mixin类放在父类前面。

# 定义Mixin类:提供“跑步”功能,只含方法,不含属性
class RunMixin:
    def run(self):
        print(f"{self.name}在跑步")

# 定义Mixin类:提供“游泳”功能
class SwimMixin:
    def swim(self):
        print(f"{self.name}在游泳")

# 子类继承Person类 + 两个Mixin类,实现功能复用
class Person:
    def __init__(self, name):
        self.name = name

class Student(Person, RunMixin, SwimMixin):
    pass

class Teacher(Person, RunMixin):
    pass

# 测试Mixin混入
stu = Student("张三")
stu.run()  # 输出:张三在跑步(继承RunMixin)
stu.swim() # 输出:张三在游泳(继承SwimMixin)

teacher = Teacher("李老师")
teacher.run()  # 输出:李老师在跑步(继承RunMixin)
# teacher.swim()  # 报错:Teacher没有swim方法(未继承SwimMixin)

优势:Mixin类可以灵活组合,子类按需继承,避免多继承的方法冲突,同时实现代码复用,是Python中大型项目常用的进阶技巧。

4.6 进阶技巧总结

  • 魔术方法:重点掌握__str__、__repr__、__add__,简化代码,提升类的灵活性。

  • @property:核心简化封装,将方法伪装成属性,实现可读可写的安全访问。

  • 抽象类:统一子类规范,适合多子类开发,需通过abc模块实现。

  • Mixin混入:优化多继承,实现功能复用,避免方法冲突。

初学者建议:先掌握@property和常用魔术方法,再逐步学习抽象类和Mixin,无需急于求成,结合实例练习即可熟练运用。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐