面向对象编程 (OOP)
面向对象编程(Object Oriented Programming,简称OOP)是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。在Python中,一切皆为对象。
面向过程 vs 面向对象
让我们通过学生成绩管理这个例子,理解两种编程范式的区别:
┌─────────────────────────────────────────────────────────────────┐
│ 面向过程 vs 面向对象 对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【面向过程 - 像做菜食谱】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 步骤1: 准备学生A的成绩数据 │ │
│ │ 步骤2: 调用 print_score() 打印 │ │
│ │ 步骤3: 准备学生B的成绩数据 │ │
│ │ 步骤4: 调用 print_score() 打印 │ │
│ │ ... │ │
│ │ │ │
│ │ 特点: 数据和函数分离,函数操作外部数据 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【面向对象 - 像养宠物】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ class Student: │ │
│ │ # 数据(name, score)和方法(print_score)在一起 │ │
│ │ │ │
│ │ 学生A = Student("小明", 95) # 创建对象 │ │
│ │ 学生A.print_score() # 给对象发消息 │ │
│ │ │ │
│ │ 特点: 数据和方法封装在一起,对象自己管理自己的数据 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
面向过程方式
Python
# 面向过程:用字典表示学生,用函数处理
std1 = {'name': '小明', 'score': 95}
std2 = {'name': '小红', 'score': 87}
def print_score(std):
print(f"{std['name']}: {std['score']}")
def get_score(std):
return std['score']
print_score(std1) # 小明: 95
print_score(std2) # 小红: 87
# 问题:如果要添加新功能,比如打印等级,需要修改多个函数
def get_grade(score):
if score >= 90: return 'A'
if score >= 80: return 'B'
return 'C'
面向对象方式
Python
# 面向对象:把数据和方法封装在类中
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print(f"{self.name}: {self.score}")
def get_grade(self):
if self.score >= 90: return 'A'
if self.score >= 80: return 'B'
return 'C'
# 创建两个学生对象
student1 = Student('小明', 95)
student2 = Student('小红', 87)
student1.print_score() # 小明: 95
student2.print_score() # 小红: 87
# 优点:添加新功能只需在类中添加方法,不影响现有代码
类 vs 实例
类(Class)是抽象的"概念",比如"学生"这个概念;
实例(Instance)是具体的"个体",比如"小明"这个具体的学生。
类是蓝图,实例是根据蓝图创建的实际对象。
类(Class)是抽象的"概念",比如"学生"这个概念;
实例(Instance)是具体的"个体",比如"小明"这个具体的学生。
类是蓝图,实例是根据蓝图创建的实际对象。
类的基本结构
┌─────────────────────────────────────────────────────────────────┐
│ 类(Class)的基本结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ class 类名: │
│ │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ # 类变量 / 类属性 (所有实例共享) │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ species = "动物" │
│ count = 0 │
│ │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ # 初始化方法 __init__ │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ def __init__(self, name, age): │
│ self.name = name # 实例变量 │
│ self.age = age # 实例变量 │
│ │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ # 实例方法 (第一个参数是 self) │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ def speak(self): │
│ return f"{self.name}在叫" │
│ │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ # 特殊方法 __str__ │
│ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ def __str__(self): │
│ return f"Student({self.name})" │
│ │
└─────────────────────────────────────────────────────────────────┘
1. 定义类
Python
class Dog:
# 类变量 - 所有实例共享
species = "犬科"
total_dogs = 0
# 初始化方法 - 创建实例时自动调用
def __init__(self, name, age):
# self 是实例对象本身
self.name = name # 实例变量 - 每个实例独有
self.age = age
Dog.total_dogs += 1 # 修改类变量
# 实例方法 - 第一个参数必须是 self
def bark(self):
return f"{self.name}说: 汪汪!"
# 实例方法可以访问和修改实例变量和类变量
def info(self):
return f"{self.name}, {self.age}岁, 是{self.species}"
# 特殊方法 - 字符串表示
def __str__(self):
return f"Dog({self.name})"
2. 创建实例
Python
# 创建 Dog 类的实例
my_dog = Dog("旺财", 3)
your_dog = Dog("小黑", 5)
# 调用实例方法
print(my_dog.bark()) # 旺财说: 汪汪!
print(your_dog.bark()) # 小黑说: 汪汪!
print(my_dog.info()) # 旺财, 3岁, 是犬科
# 访问类变量
print(Dog.species) # 犬科 (通过类访问)
print(my_dog.species) # 犬科 (通过实例访问)
# 类变量是共享的
print(Dog.total_dogs) # 2
# 使用 __str__ 方法
print(my_dog) # Dog(旺财)
3. self 参数详解
self 是类方法的一个特殊参数,它指向当前实例对象。通过 self,方法可以访问和修改实例的属性。
┌─────────────────────────────────────────────────────────────────┐
│ self 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ my_dog = Dog("旺财", 3) │
│ │
│ 当调用 my_dog.bark() 时: │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Python 内部做的是: │ │
│ │ │ │
│ │ Dog.bark(my_dog) # 转换为类方法调用 │ │
│ │ │ │
│ │ 所以 self.name 在这里就是 "旺财" │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 内存示意: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ my_dog ----→ Dog 实例对象 │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ name = "旺财" │ │ │
│ │ │ age = 3 │ │ │
│ │ │ species = "犬科" (共享) │ │ │
│ │ └──────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
封装 - 数据保护
封装是将数据和操作数据的方法绑定在一起,并隐藏内部实现细节。对外提供接口,限制对内部数据的直接访问。
┌─────────────────────────────────────────────────────────────────┐
│ 封装 (Encapsulation) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【没有封装】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ user.password = "123456" # 任何人都可以随意修改! │ │
│ │ user.balance = -1000 # 可以设置为负数! │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【有封装 - 受保护的数据】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ class User: │ │
│ │ def __init__(self): │ │
│ │ self.__password = "xxx" # 私有属性 │ │
│ │ self.__balance = 0 # 私有属性 │ │
│ │ │ │
│ │ def set_balance(self, amount): │ │
│ │ if amount < 0: # 添加验证 │ │
│ │ raise ValueError("不能为负") │ │
│ │ self.__balance = amount │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
访问控制级别
Python
class User:
def __init__(self, name, age):
self.name = name # 公开属性 - 可以在任何地方访问
self._age = age # 受保护属性 - 约定不直接外部访问
self.__password = "123" # 私有属性 - 会在外部被改名
# 公开方法访问私有属性
def get_password(self):
return "****" # 不返回真实密码
def set_password(self, new_pwd):
if len(new_pwd) < 6:
raise ValueError("密码长度至少6位")
self.__password = new_pwd
user = User("张三", 25)
# 访问公开属性
print(user.name) # 张三
# 访问受保护属性 (可以访问,但不推荐)
print(user._age) # 25
# 访问私有属性 - 会失败
# print(user.__password) # AttributeError!
# Python 名称重整后可以访问,但不推荐
print(user._User__password) # 123 (不推荐)
# 通过公开方法访问
print(user.get_password()) # ****
Python 的访问控制是"约定"而非"强制"
Python 没有真正的 private 修饰符,但通过名称重整(Name Mangling)机制,
Python 没有真正的 private 修饰符,但通过名称重整(Name Mangling)机制,
__password 会变成 _ClassName__password,在一定程度上提示开发者不要直接访问。
继承 - 代码复用
继承允许我们定义一个类(子类)继承另一个类(父类)的属性和方法,实现代码复用。
┌─────────────────────────────────────────────────────────────────┐
│ 继承关系示意图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【动物 Animal】 │
│ - name │
│ - eat() │
│ - speak() │
│ ▲ │
│ │ 继承 │
│ ┌───────────────┴───────────────┐ │
│ │ │ │
│ 【狗 Dog】 【猫 Cat】 │
│ - breed - color │
│ - bark() ← 重写 speak() - meow() ← 重写 speak()│
│ │
│ 继承语法: │
│ class 子类(父类): │
│ pass │
│ │
│ 子类会自动获得父类的所有属性和方法 │
│ 子类可以重写(override)父类的方法 │
│ │
└─────────────────────────────────────────────────────────────────┘
1. 基本继承
Python
# 父类 (基类)
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name}正在吃东西"
def speak(self):
pass # 子类必须重写此方法
# 子类 (派生类)
class Dog(Animal):
# 子类可以添加自己的属性
def __init__(self, name, breed):
# 调用父类的 __init__
super().__init__(name)
self.breed = breed
# 重写父类的方法
def speak(self):
return f"{self.name}说: 汪汪!"
# 子类新增的方法
def guard(self):
return f"{self.name}正在看家"
# 创建子类实例
dog = Dog("旺财", "金毛")
# 继承自父类的方法
print(dog.eat()) # 旺财正在吃东西
# 子类重写的方法
print(dog.speak()) # 旺财说: 汪汪!
# 子类自己的方法
print(dog.guard()) # 旺财正在看家
# 子类的属性
print(dog.breed) # 金毛
2. super() 函数
super() 允许子类调用父类的方法,实现代码复用。
Python
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
class Dog(Animal):
def __init__(self, name, age, breed):
# 调用父类的 __init__,复用父类的初始化逻辑
super().__init__(name, age)
self.breed = breed
class Cat(Animal):
def __init__(self, name, age, color):
super().__init__(name, age) # 同样调用父类
self.color = color
# 测试
dog = Dog("旺财", 3, "金毛")
cat = Cat("咪咪", 2, "白色")
print(f"狗: {dog.name}, {dog.age}岁, {dog.breed}")
print(f"猫: {cat.name}, {cat.age}岁, {cat.color}")
3. 多继承
Python 支持多继承,一个子类可以继承多个父类。
Python
# 多个父类
class Flyable:
def fly(self):
return f"{self.name}正在飞"
class Swimable:
def swim(self):
return f"{self.name}正在游泳"
# 子类继承多个父类
class Duck(Flyable, Swimable):
def __init__(self, name):
self.name = name
duck = Duck("唐老鸭")
print(duck.fly()) # 唐老鸭正在飞
print(duck.swim()) # 唐老鸭正在游泳
# 方法解析顺序 (MRO)
print(Duck.__mro__)
多态 - 同一接口,不同实现
多态允许我们使用父类类型的引用指向子类对象,调用时根据实际对象类型决定执行哪个方法。
┌─────────────────────────────────────────────────────────────────┐
│ 多态 (Polymorphism) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【概念】同一个接口,不同的实现 │
│ │
│ ┌─────────┐ │
│ │ speak() │ ← 统一接口 │
│ └────┬────┘ │
│ │ │
│ ┌──────┼──────┐ │
│ ▼ ▼ ▼ │
│ 狗叫 猫叫 鸟叫 │
│ "汪汪" "喵喵" "叽叽" │
│ │
│ 【优势】代码灵活,易于扩展 │
│ │
│ def make_speak(animal): │
│ print(animal.speak()) # 不需要知道具体是什么动物 │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("子类必须实现 speak() 方法")
class Dog(Animal):
def speak(self):
return f"{self.name}说: 汪汪!"
class Cat(Animal):
def speak(self):
return f"{self.name}说: 喵喵!"
class Duck(Animal):
def speak(self):
return f"{self.name}说: 嘎嘎!"
# 多态:同一个函数,传入不同对象,执行不同行为
def make_speak(animals):
for animal in animals:
print(animal.speak())
# 创建不同类型的动物
animals = [Dog("旺财"), Cat("咪咪"), Duck("唐老鸭")]
# 调用同一个函数,显示不同结果
make_speak(animals)
# 输出:
# 旺财说: 汪汪!
# 咪咪说: 喵喵!
# 唐老鸭说: 嘎嘎!
多态的优势
1. 可扩展性:添加新动物类型时,不需要修改 make_speak() 函数
2. 解耦合:调用者只需要知道 speak() 方法存在,不需要关心具体类型
3. 灵活性:同一份代码,可以处理不同类型的对象
1. 可扩展性:添加新动物类型时,不需要修改 make_speak() 函数
2. 解耦合:调用者只需要知道 speak() 方法存在,不需要关心具体类型
3. 灵活性:同一份代码,可以处理不同类型的对象
特殊方法 (Magic Methods)
Python 类有一系列以双下划线开头和结尾的特殊方法,也称为"魔法方法",用于实现特定功能。
Python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# 字符串表示 - print() 和 str() 使用
def __str__(self):
return f"Vector({self.x}, {self.y})"
# 正式字符串表示 - repr() 使用
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# 加法运算
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# 长度
def __len__(self):
return 2
# 索引访问
def __getitem__(self, index):
if index == 0: return self.x
if index == 1: return self.y
raise IndexError("索引超出范围")
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1) # Vector(2, 3)
print(v1 + v2) # Vector(6, 8)
print(len(v1)) # 2
print(v1[0], v1[1]) # 2 3
| 特殊方法 | 作用 | 示例 |
|---|---|---|
__init__ |
初始化实例 | 创建对象时自动调用 |
__str__ |
字符串表示 | print(obj) 时调用 |
__repr__ |
正式字符串 | repr(obj) 时调用 |
__add__ |
加法运算 | obj1 + obj2 |
__len__ |
长度 | len(obj) |
__eq__ |
相等比较 | obj1 == obj2 |
知识测验
问题1:以下代码的输出是什么?
class A:
x = 1
def __init__(self, y):
self.y = y
a = A(10)
print(a.x, a.y)
问题2:关于继承和多态,哪个说法是正确的?
问题3:以下哪个是私有属性的正确写法?
课后练习
练习1:设计银行账户类
设计一个银行账户类:
- 属性:账号、姓名、余额(私有)
- 方法:存款(验证金额为正)、取款(验证余额充足)、查询余额
- 使用封装保护余额不被直接修改
- 添加转账功能(从本账户转出到另一个账户)
练习2:图形继承体系
设计一个图形继承体系:
- 基类 Shape,有属性 name 和方法 area()、perimeter()
- 子类 Circle(需要半径)、Rectangle(需要长宽)
- 子类 Square(继承 Rectangle,只需要一个边长)
- 创建多态函数 print_shape_info(),接收任意 Shape 对象
练习3:实现特殊方法
创建一个复数类 Complex:
- 属性:实部 real,虚部 imag
- 实现
__add__(复数加法) - 实现
__str__(显示格式如 3+4j) - 实现
__eq__(判断两个复数是否相等) - 测试复数的加减和比较
章节总结
┌─────────────────────────────────────────────────────────────────┐
│ 本章要点 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 面向对象概念 │
│ • 类(Class):抽象模板,定义对象的共同属性和方法 │
│ • 实例(Instance):根据类创建的具体对象 │
│ • OOP三要素:封装、继承、多态 │
│ │
│ 2. 类定义 │
│ • class 类名: │
│ • __init__():初始化方法 │
│ • self:指向实例本身的引用 │
│ │
│ 3. 继承 │
│ • class 子类(父类): │
│ • super().__init__():调用父类构造方法 │
│ • 多继承:class 子类(父类1, 父类2): │
│ │
│ 4. 多态 │
│ • 同一接口,不同实现 │
│ • 增强代码的可扩展性和灵活性 │
│ │
│ 5. 访问控制 │
│ • 公开:name │
│ • 受保护(约定):_name │
│ • 私有(重整):__name │
│ │
│ 6. 特殊方法 │
│ • __str__:字符串表示 │
│ • __repr__:正式字符串 │
│ • __add__:加法运算 │
│ │
└─────────────────────────────────────────────────────────────────┘
🐍 Python 在线代码编辑器
📝 运行结果