【Python学习】面向对象程序设计
目录
4.2.2 可修改的@property(搭配@属性名.setter)
4.2.3 可删除的@property(搭配@属性名.deleter)
前言
面向对象程序设计(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 访问对象的属性
访问对象的属性有两种方式:
-
对象名.属性名(最常用)
-
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 什么是多态
多态的核心思想是:同一方法调用,由于对象不同,会产生不同的执行结果。换句话说,子类继承父类的方法后,重写该方法,当调用该方法时,传入不同的子类对象,会执行不同的逻辑。
多态的前提:
-
存在继承关系(子类继承父类)。
-
子类重写父类的方法。
举个例子:“动物”类有一个“叫”的方法,“猫”类和“狗”类继承“动物”类,并重写“叫”的方法,当调用“叫”的方法时,猫会“喵喵叫”,狗会“汪汪叫”,这就是多态。
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,无需急于求成,结合实例练习即可熟练运用。
更多推荐



所有评论(0)