第5章 面向对象
本章学习目标
学完本章后,你应该能够:
1. 解释对象、类、封装、继承、多态等基本概念
2. 使用 class 定义一个简单的 Python 类
3. 区分类属性与实例属性
4. 理解并使用 __init__() 与 __str__() 等常见魔法方法
5. 编写类方法与静态方法
6. 使用继承、方法重写和多态组织代码
目录
- 面向对象编程介绍
- 类与对象
- 魔法方法
- 类方法和静态方法
- 继承、重写与多态
- 本章总结与练习
1. 面向对象编程介绍
面向对象编程(Object-Oriented Programming, OOP)是一种组织程序的思想。
它强调:把现实世界中的事物抽象成“对象”,再让对象之间协作完成任务。
在面向对象思想中,我们不只是写“步骤”,而是更关注:
- 这个系统里有哪些“对象”?
- 每个对象有什么“属性”?
- 每个对象能做什么“行为”?
- 对象之间是什么关系?
例如,在“学生管理系统”中,我们可以把“学生”看作一个对象: - 属性:姓名、学号、年龄、成绩 - 行为:学习、选课、打印成绩单
1.1 面向对象的主要概念
(1)对象
万物皆对象。
任何一个操作或业务逻辑,最终都需要由某个“实体”来完成,这个实体就是对象。
对象通常由两部分组成:
- 属性:描述对象“是什么”,偏静态
例如:姓名、年龄、颜色、重量
- 方法:描述对象“能做什么”,偏动态
例如:吃饭、奔跑、打印信息
(2)类
类是对一群具有相同特征和行为的事物的抽象。
可以把类理解成“模板”或“蓝图”,而对象是根据类创建出来的“实例”。
例如:
- 类:Person
- 对象:张三、李四、王五
也就是说: - 类解决“这一类东西长什么样、会做什么” - 对象解决“具体是哪一个”
1.2 面向对象的三个特征
1)封装(Encapsulation)
把属性和方法组织到一个类中,对外暴露必要接口,隐藏内部实现细节。
作用: - 保护数据 - 降低耦合 - 便于维护
2)继承(Inheritance)
子类可以复用父类已有的属性和方法。
作用: - 代码复用 - 结构清晰 - 便于扩展
3)多态(Polymorphism)
同样的方法调用,作用于不同对象时,可以表现出不同的行为。
作用: - 提高程序灵活性 - 让程序更容易扩展
1.3 一个直观类比
以“动物”为例:
- 类:
Animal - 对象:猫、狗、鸟
- 属性:名字、年龄、颜色
- 方法:叫、跑、吃
如果继续细分:
- Dog 可以继承 Animal
- Cat 也可以继承 Animal
- 它们都可以有 speak() 方法
- 但 Dog.speak() 与 Cat.speak() 的表现不同
这就是后面要讲的继承与多态。
2. 类与对象
2.1 创建类与实例对象
Python 中定义类的基本语法如下:
创建对象(实例)的语法如下:
创建完成后,可以通过 . 访问对象的属性和方法:
class Animal:
name = '动物'
def introduce(self):
print("我是一只动物。")
a = Animal()
print(a.name)
a.introduce()
动物
我是一只动物。
讲解
上面的代码中:
Animal是一个类a是Animal的一个实例对象name是类中的一个属性introduce()是类中的一个方法self表示“当前对象本身”
在调用
a.introduce()时,Python 会自动把a作为第一个参数传给self
2.2 类属性与实例属性
(1)类属性
类属性是直接定义在类中的属性,属于“整个类”,通常被所有实例共享。
例如,上面的 name = '动物' 就是类属性。
当修改类属性时,所有未单独覆盖该属性的实例都会看到变化。
class Animal:
name = '动物'
def introduce(self):
print("我是一只动物。")
a = Animal()
b = Animal()
b.name = "dog"
print("修改前:", Animal.name, a.name, b.name)
Animal.name = "猫"
print("修改后:", Animal.name, a.name, b.name)
修改前: 动物 动物 dog
修改后: 猫 猫 dog
观察与思考
因为 a.name 和 b.name 默认都会去类中查找 name,
所以当我们修改 Animal.name 后,两个实例看到的值也一起变化了。
这说明:类属性属于类本身,被实例共享。
2.3 实例属性
实例属性属于“某一个具体对象”,通常通过:
- 在 __init__() 中定义
- 或者直接通过 对象名.属性 = 值 动态增加
下面先看动态增加属性的方式。
class Animal:
__name = '动物' # 私有类属性
def introduce(self):
print(f"我是一只{self.__name}。")
a = Animal()
a.weight = 2 # 动态给实例添加属性
print("a.weight =", a.weight)
a.introduce()
a.weight = 2
我是一只动物。
讲解
这里的 weight 只属于对象 a,如果我们再创建一个对象 b,它默认并没有这个属性。
b = Animal()
print("a 是否有 weight?", hasattr(a, "weight"))
print("b 是否有 weight?", hasattr(b, "weight"))
a 是否有 weight? True
b 是否有 weight? False
2.4 公有属性与私有属性
在 Python 中,约定上:
- 普通名字:通常视为公有属性
- 以两个下划线开头:会触发名称重整(name mangling),常用于“私有化”处理
例如:
这表示不希望在类外部直接访问 __name。
class Animal:
__name = "动物"
def introduce(self):
print(f"我是一只{self.__name}。")
a = Animal()
a.introduce()
# print(a.__name)
# 直接访问私有属性会报错,先演示安全写法
print("对象字典:", dir(a)[:10], "...")
我是一只动物。
对象字典: ['_Animal__name', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__'] ...
注意:Python 的“私有”更准确地说是一种访问限制机制/命名机制,
不是像某些语言那样的绝对私有。
3. 魔法方法
Python 中有一类特殊方法,名字形如:
这类方法通常在特定场景下被 Python 自动调用,因此常称为: - 魔法方法 - 特殊方法 - 双下划线方法
常见例子:
- __init__():初始化对象
- __str__():定义对象的字符串显示形式
- __len__():定义 len(对象) 的行为
- __del__():对象销毁时调用(一般不建议依赖)
3.1 __init__() —— 构造方法
当我们创建对象时,__init__() 会自动执行。
它最常见的作用就是:给实例属性赋初值。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"大家好,我叫{self.name},今年{self.age}岁。")
s1 = Student("小明", 18)
s2 = Student("小红", 19)
s1.introduce()
s2.introduce()
大家好,我叫小明,今年18岁。
大家好,我叫小红,今年19岁。
讲解
这里:
- self.name 和 self.age 是实例属性
- 每创建一个对象,都可以拥有自己的姓名和年龄
- 这也是实例属性最常见的创建方式
3.2 __str__() —— 字符串表示方法
如果直接打印一个普通对象,通常会看到类似内存地址的信息,不够友好。
我们可以通过 __str__() 自定义对象被 print() 时的显示内容。
class Book:
def __init__(self, title, price):
self.title = title
self.price = price
def __str__(self):
return f"《{self.title}》- 价格:{self.price}元"
b = Book("Python程序设计", 59)
print(b)
《Python程序设计》- 价格:59元
讲解
当执行 print(b) 时,Python 会自动调用 b.__str__()。
这使得对象输出更直观,也更适合调试和展示。
3.3 再看一个更完整的类设计
下面我们把 __init__() 和 __str__() 结合起来,构造一个更规范的类。
class Animal:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def eat(self):
print(f"{self.name}正在吃东西。")
def __str__(self):
return f"Animal(name={self.name}, weight={self.weight})"
cat = Animal("小花猫", 4.2)
print(cat)
cat.eat()
Animal(name=小花猫, weight=4.2)
小花猫正在吃东西。
4. 类方法和静态方法
除了普通实例方法外,Python 还支持:
- 类方法 @classmethod
- 静态方法 @staticmethod
它们和普通方法的区别,核心在于:它们依赖谁来调用、能够访问什么数据。
4.1 实例方法、类方法、静态方法对比
1)实例方法
- 第一个参数是
self - 由对象调用
- 可以访问实例属性,也可以访问类属性
2)类方法
- 使用
@classmethod - 第一个参数通常写成
cls - 可以由类直接调用
- 可以访问类属性,但不能直接访问某个具体实例的属性
3)静态方法
- 使用
@staticmethod - 不需要
self或cls - 更像是“写在类里的普通函数”
- 通常用于和类相关、但又不依赖实例或类状态的功能
class Teacher:
school = "示例大学"
def __init__(self, name):
self.name = name
def instance_method(self):
print(f"实例方法:我是{self.name},来自{self.school}")
@classmethod
def class_method(cls):
print(f"类方法:学校是{cls.school}")
@staticmethod
def static_method():
print("静态方法:这是一段与具体对象无关的工具逻辑")
t = Teacher("王老师")
t.instance_method()
t.class_method()
t.static_method()
print("-" * 30)
Teacher.class_method()
Teacher.static_method()
实例方法:我是王老师,来自示例大学
类方法:学校是示例大学
静态方法:这是一段与具体对象无关的工具逻辑
------------------------------
类方法:学校是示例大学
静态方法:这是一段与具体对象无关的工具逻辑
4.2 类方法示例:统计创建了多少个对象
类方法常用于操作类级别的数据,比如统计对象数量。
class User:
count = 0
def __init__(self, name):
self.name = name
User.count += 1
@classmethod
def show_count(cls):
print(f"当前共创建了 {cls.count} 个用户对象")
u1 = User("张三")
u1.show_count()
u2 = User("李四")
u1.show_count()
u3 = User("王五")
# User.show_count()
u1.show_count()
当前共创建了 1 个用户对象
当前共创建了 2 个用户对象
当前共创建了 3 个用户对象
4.3 静态方法示例:工具函数
静态方法适合封装一些“逻辑上属于这个类,但不依赖实例状态”的功能。
class MathTool:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(n):
return n % 2 == 0
print(MathTool.add(3, 5))
print(MathTool.is_even(10))
5. 继承、重写与多态
这是面向对象中非常重要的一部分。
它们能让程序结构更清晰,也更容易扩展。
5.1 继承
继承的语法:
子类会继承父类的公有属性和公有方法。
这样可以减少重复代码,并且让“同一类事物”的层次关系更加清晰。
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}正在吃东西。")
class Dog(Animal):
def bark(self):
print(f"{self.name}正在汪汪叫。")
d = Dog("旺财")
d.eat() # 继承自父类
d.bark() # 子类自己的方法
旺财正在吃东西。
旺财正在汪汪叫。
讲解
在这个例子中:
- Animal 是父类
- Dog 是子类
- Dog 自动拥有了 Animal 的 __init__() 和 eat()
- 同时 Dog 还能扩展自己的 bark()
5.2 方法重写(Override)
如果父类中的某个方法不完全适合子类,
就可以在子类中定义同名方法进行重写。
class Animal:
def speak(self):
print("动物会发出声音。")
class Dog(Animal):
def speak(self):
print("小狗:汪汪汪!")
class Cat(Animal):
def speak(self):
print("小猫:喵喵喵!")
a = Animal()
d = Dog()
c = Cat()
a.speak()
d.speak()
c.speak()
动物会发出声音。
小狗:汪汪汪!
小猫:喵喵喵!
讲解
虽然这三个类都有 speak() 方法,但它们的行为不同。
这正是后面“多态”的基础。
5.3 super() 的使用
在子类中,如果既想保留父类原有逻辑,又想增加自己的逻辑,
可以使用 super() 调用父类方法。
class Animal:
def __init__(self, name):
self.name = name
def introduce(self):
print(f"我是动物,我叫{self.name}。")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类构造方法
self.breed = breed
def introduce(self):
super().introduce() # 调用父类方法
print(f"我是一只{self.breed}。")
dog = Dog("豆豆", "金毛")
dog.introduce()
我是动物,我叫豆豆。
我是一只金毛。
5.4 多态
多态强调的是:
相同的方法调用,面对不同对象,可以产生不同结果。
这使得我们在写程序时,可以面向“统一接口”编程,而不是对每种对象都写一套逻辑。
class Dog:
def speak(self):
print("狗:汪汪汪")
class Cat:
def speak(self):
print("猫:喵喵喵")
class Bird:
def speak(self):
print("鸟:叽叽喳喳")
def animal_speak(animal):
animal.speak()
animals = [Dog(), Cat(), Bird()]
for animal in animals:
animal_speak(animal)
狗:汪汪汪
猫:喵喵喵
鸟:叽叽喳喳
讲解
这里的 animal_speak() 并不关心传进来的到底是狗、猫还是鸟。
它只要求:这个对象有 speak() 方法。
这就是多态带来的灵活性。
5.5 一个综合示例:图形面积计算
下面用“图形”来演示继承 + 重写 + 多态的组合。
class Shape:
def area(self):
raise NotImplementedError("子类必须重写 area() 方法")
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
def print_area(shape):
print(f"面积 = {shape.area():.2f}")
shapes = [Rectangle(3, 4), Circle(2)]
for s in shapes:
print_area(s)
面积 = 12.00
面积 = 12.56
教学提示
这个例子很适合课堂总结:
- Shape 代表抽象父类
- Rectangle 和 Circle 重写了 area()
- print_area() 不需要区分到底是哪种图形
- 这正是多态的实际价值
6. 本章总结
本章围绕 Python 面向对象编程展开,核心内容如下:
6.1 关键概念回顾
- 对象:具有属性和行为的实体
- 类:创建对象的模板
- 封装:把数据与操作组织在一起,对外提供接口
- 继承:子类复用父类代码
- 多态:同一调用在不同对象上表现不同
6.2 技术点回顾
- 使用
class定义类 - 使用对象访问属性和方法
- 区分类属性与实例属性
- 使用
__init__()初始化对象 - 使用
__str__()优化输出 - 理解类方法和静态方法
- 通过继承、重写和多态提升代码复用性和扩展性
6.3 一句话理解本章
面向对象编程,就是把程序拆成一个个“有状态、能行动”的对象,让它们协作完成任务。
7. 课堂练习
练习 1:创建一个学生类
要求:
1. 定义 Student 类
2. 包含姓名、年龄两个属性
3. 定义 introduce() 方法输出个人信息
可以先自己写,再参考答案。
# 练习 1 参考答案
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"我叫{self.name},今年{self.age}岁。")
stu = Student("小华", 20)
stu.introduce()
练习 2:设计一个汽车类
要求:
1. 定义 Car 类
2. 属性:品牌、颜色
3. 方法:run() 输出“某品牌汽车正在行驶”
试着补全下面代码。
# 练习 2 参考答案
class Car:
def __init__(self, brand, color):
self.brand = brand
self.color = color
def run(self):
print(f"{self.color}的{self.brand}汽车正在行驶。")
car = Car("比亚迪", "白色")
car.run()
练习 3:继承与重写
要求:
1. 定义父类 Animal
2. 定义子类 Dog、Cat
3. 每个子类都重写 speak() 方法
# 练习 3 参考答案
class Animal:
def speak(self):
print("动物发出声音")
class Dog(Animal):
def speak(self):
print("汪汪汪")
class Cat(Animal):
def speak(self):
print("喵喵喵")
for animal in [Dog(), Cat()]:
animal.speak()
8. 课后思考题
- 类属性和实例属性的本质区别是什么?
- 为什么
__init__()中通常使用self.属性名 = 值? - 类方法和静态方法分别适合什么场景?
- 为什么说多态能提高程序的扩展性?
- 在实际项目中,什么时候应该使用继承,什么时候更适合组合?