Python面向对象编程:class比C的struct强在哪?继承、多态一篇讲清
面向对象编程(OOP)是一种重要的编程范式。虽然Python支持多种编程风格,但理解OOP对于阅读他人代码和使用第三方库非常重要。本篇标记为选修/后置,你可以在掌握基础后按需学习。
1. 面向对象基础概念
| 概念 | 说明 | 类比 |
|---|---|---|
| 类(Class) | 对象的模板/蓝图 | 汽车设计图 |
| 对象(Object) | 类的实例 | 具体的一辆汽车 |
| 属性(Attribute) | 对象的数据 | 汽车的颜色、品牌 |
| 方法(Method) | 对象的行为 | 汽车的启动、刹车 |
| 封装(Encapsulation) | 隐藏内部实现 | 不需要知道发动机原理也能开车 |
| 继承(Inheritance) | 子类继承父类特性 | 电动汽车继承汽车的基本特性 |
| 多态(Polymorphism) | 同一接口不同实现 | 不同品牌汽车的启动方式 |
2. 类与对象
2.1 定义类
class Person:
"""人员类"""
# 类属性(所有实例共享)
species = "人类"
# 初始化方法(构造函数)
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 实例方法
def introduce(self):
return f"我是{self.name},今年{self.age}岁"
def have_birthday(self):
self.age += 1
print(f"{self.name}过生日了,现在{self.age}岁")
2.2 创建对象
# 创建对象(实例化)
person1 = Person("张三", 25)
person2 = Person("李四", 30)
# 访问属性
print(person1.name) # 张三
print(person1.age) # 25
print(person1.species) # 人类
# 调用方法
print(person1.introduce()) # 我是张三,今年25岁
person1.have_birthday() # 张三过生日了,现在26岁
# 修改属性
person1.age = 28
person1.city = "北京" # 动态添加属性
2.3 实例属性和类属性
class Counter:
# 类属性:所有实例共享
total_count = 0
def __init__(self, name):
# 实例属性:每个实例独有
self.name = name
self.count = 0
Counter.total_count += 1
def increment(self):
self.count += 1
# 测试
c1 = Counter("计数器1")
c2 = Counter("计数器2")
c1.increment()
c1.increment()
c2.increment()
print(c1.count) # 2(实例属性)
print(c2.count) # 1(实例属性)
print(Counter.total_count) # 2(类属性)
3. 方法类型
class MyClass:
class_var = "类变量"
def __init__(self, value):
self.instance_var = value
# 实例方法:第一个参数是self
def instance_method(self):
return f"实例方法,访问{self.instance_var}"
# 类方法:第一个参数是cls
@classmethod
def class_method(cls):
return f"类方法,访问{cls.class_var}"
# 静态方法:没有self或cls
@staticmethod
def static_method(x, y):
return f"静态方法,计算{x + y}"
# 使用
obj = MyClass("实例变量值")
print(obj.instance_method()) # 实例方法
print(MyClass.class_method()) # 类方法
print(obj.class_method()) # 也可以通过实例调用
print(MyClass.static_method(1, 2)) # 静态方法
| 方法类型 | 装饰器 | 第一个参数 | 用途 |
|---|---|---|---|
| 实例方法 | 无 | self | 操作实例数据 |
| 类方法 | @classmethod | cls | 操作类数据,工厂方法 |
| 静态方法 | @staticmethod | 无 | 工具函数,与类相关但不需要访问类/实例 |
4. 封装
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # 公开属性
self._balance = balance # 约定私有(单下划线)
self.__pin = "1234" # 名称修饰(双下划线)
# 属性访问器(getter)
@property
def balance(self):
return self._balance
# 属性设置器(setter)
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("余额不能为负")
self._balance = value
def deposit(self, amount):
if amount > 0:
self._balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance -= amount
return True
return False
# 使用
account = BankAccount("张三", 1000)
print(account.balance) # 1000(通过property访问)
account.balance = 2000 # 通过setter设置
# account.balance = -100 # ValueError
account.deposit(500)
print(account.balance) # 2500
# 访问约定私有属性(不推荐但可以)
print(account._balance) # 2500
# 访问名称修饰属性
# print(account.__pin) # AttributeError
print(account._BankAccount__pin) # 1234(可以但强烈不推荐)
5. 继承
# 父类(基类)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("子类必须实现此方法")
def move(self):
print(f"{self.name}在移动")
# 子类
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类构造函数
self.breed = breed
def speak(self):
return f"{self.name}说:汪汪!"
def fetch(self):
return f"{self.name}在捡球"
class Cat(Animal):
def speak(self):
return f"{self.name}说:喵喵!"
# 使用
dog = Dog("旺财", "金毛")
cat = Cat("咪咪")
print(dog.speak()) # 旺财说:汪汪!
print(cat.speak()) # 咪咪说:喵喵!
dog.move() # 旺财在移动(继承的方法)
print(dog.fetch()) # 旺财在捡球(子类特有方法)
# 检查继承关系
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(issubclass(Dog, Animal)) # True
# 多重继承
class FlyingAnimal:
def fly(self):
print("飞行中...")
class Bird(Animal, FlyingAnimal):
def speak(self):
return f"{self.name}说:叽叽!"
bird = Bird("小鸟")
bird.fly() # 飞行中...
6. 多态
class Shape:
def area(self):
raise NotImplementedError
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):
import math
return math.pi * self.radius ** 2
# 多态:同一接口,不同实现
def print_area(shape):
print(f"面积:{shape.area():.2f}")
shapes = [Rectangle(3, 4), Circle(5), Rectangle(2, 6)]
for shape in shapes:
print_area(shape)
# 面积:12.00
# 面积:78.54
# 面积:12.00
# Python的鸭子类型
# "如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子"
class Duck:
def quack(self):
print("嘎嘎")
class Person:
def quack(self):
print("我在模仿鸭子:嘎嘎")
def make_it_quack(thing):
thing.quack() # 不关心类型,只关心有没有quack方法
make_it_quack(Duck()) # 嘎嘎
make_it_quack(Person()) # 我在模仿鸭子:嘎嘎
7. 魔术方法
魔术方法(Magic Methods)是以双下划线开头和结尾的特殊方法。
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# 字符串表示
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
# 运算符重载
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# 比较运算
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
# 长度
def __len__(self):
return 2
# 索引访问
def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
raise IndexError("索引超出范围")
# 布尔值
def __bool__(self):
return self.x != 0 or self.y != 0
# 使用
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1) # Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 2) # Vector(6, 8)
print(v1 == Vector(3, 4)) # True
print(v1[0]) # 3
print(len(v1)) # 2
print(bool(Vector(0, 0))) # False
| 魔术方法 | 用途 | 触发方式 |
|---|---|---|
__init__ | 初始化 | obj = Class() |
__str__ | 字符串表示 | str(obj), print(obj) |
__repr__ | 开发者表示 | repr(obj) |
__add__ | 加法 | obj1 + obj2 |
__eq__ | 相等比较 | obj1 == obj2 |
__len__ | 长度 | len(obj) |
__getitem__ | 索引访问 | obj[i] |
__iter__ | 迭代 | for x in obj |
__call__ | 调用 | obj() |
8. 数据类(dataclass)
Python 3.7+引入的dataclass简化了数据类的定义。
from dataclasses import dataclass, field
from typing import List
@dataclass
class Point:
x: float
y: float
# 自动生成__init__, __repr__, __eq__等方法
p1 = Point(3, 4)
p2 = Point(3, 4)
print(p1) # Point(x=3, y=4)
print(p1 == p2) # True
@dataclass
class Person:
name: str
age: int
city: str = "北京" # 默认值
hobbies: List[str] = field(default_factory=list) # 可变默认值
person = Person("张三", 25)
print(person) # Person(name='张三', age=25, city='北京', hobbies=[])
# 不可变数据类
@dataclass(frozen=True)
class ImmutablePoint:
x: float
y: float
p = ImmutablePoint(1, 2)
# p.x = 3 # FrozenInstanceError
# 带方法的数据类
@dataclass
class Rectangle:
width: float
height: float
@property
def area(self):
return self.width * self.height
rect = Rectangle(3, 4)
print(rect.area) # 12
9. 与C语言结构体对比
// C语言结构体
struct Person {
char name[50];
int age;
char city[50];
};
// 使用
struct Person p1;
strcpy(p1.name, "张三");
p1.age = 25;
strcpy(p1.city, "北京");
# Python类
class Person:
def __init__(self, name, age, city="北京"):
self.name = name
self.age = age
self.city = city
def introduce(self):
return f"我是{self.name}"
# 或使用dataclass
@dataclass
class Person:
name: str
age: int
city: str = "北京"
# 使用
p1 = Person("张三", 25)
| 特性 | C结构体 | Python类 |
|---|---|---|
| 数据封装 | 仅数据 | 数据+方法 |
| 类型检查 | 编译时 | 运行时(可选类型提示) |
| 内存管理 | 手动 | 自动 |
| 继承 | 不支持 | 支持 |
| 方法 | 不支持 | 支持 |
10. 实战练习
练习:简单的任务管理器
"""
练习:实现一个简单的任务管理器
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional
from enum import Enum
class TaskStatus(Enum):
PENDING = "待处理"
IN_PROGRESS = "进行中"
COMPLETED = "已完成"
@dataclass
class Task:
title: str
description: str = ""
status: TaskStatus = TaskStatus.PENDING
created_at: datetime = field(default_factory=datetime.now)
completed_at: Optional[datetime] = None
def complete(self):
self.status = TaskStatus.COMPLETED
self.completed_at = datetime.now()
def start(self):
self.status = TaskStatus.IN_PROGRESS
class TaskManager:
def __init__(self):
self._tasks: List[Task] = []
def add_task(self, title: str, description: str = "") -> Task:
task = Task(title, description)
self._tasks.append(task)
return task
def get_tasks(self, status: Optional[TaskStatus] = None) -> List[Task]:
if status is None:
return self._tasks.copy()
return [t for t in self._tasks if t.status == status]
def get_pending_count(self) -> int:
return len(self.get_tasks(TaskStatus.PENDING))
def __len__(self):
return len(self._tasks)
def __iter__(self):
return iter(self._tasks)
# 使用
if __name__ == "__main__":
manager = TaskManager()
task1 = manager.add_task("学习Python", "完成面向对象章节")
task2 = manager.add_task("写代码", "实现任务管理器")
task1.start()
task2.complete()
print(f"总任务数:{len(manager)}")
print(f"待处理:{manager.get_pending_count()}")
for task in manager:
print(f"- {task.title}: {task.status.value}")
11. 总结
🔑 核心要点
| 知识点 | 要点 |
|---|---|
| 类与对象 | 类是模板,对象是实例 |
| 属性 | 实例属性(self.x)和类属性 |
| 方法 | 实例方法、类方法、静态方法 |
| 封装 | 使用_和__约定私有,property控制访问 |
| 继承 | 子类继承父类,super()调用父类 |
| 多态 | 同一接口不同实现,鸭子类型 |
| 魔术方法 | 自定义运算符和内置函数行为 |
| dataclass | 简化数据类定义 |
✅ 学习检查清单
- 能定义类和创建对象
- 理解实例属性和类属性的区别
- 掌握三种方法类型
- 理解封装和property的使用
- 能实现简单的继承
- 了解常用魔术方法
- 能使用dataclass
📖 下一步学习
掌握了面向对象编程后,让我们学习Python的迭代器与生成器:
常见问题 FAQ
💬 Python写脚本也需要用class吗?
不一定。简单脚本用函数就够了。class适合需要管理状态的场景,或者你需要复用代码和构建大型项目时。但读懂class是必须的,因为第三方库大量使用OOP。
💬 Python有私有变量吗?
没有真正的private。_name是约定的”内部使用”,__name会触发名称改写(name mangling),但仍可通过_ClassName__name访问。Python信奉”我们都是成年人”。
� 系列导航
- 上一篇:12 - Python模块与包
- 当前:13 - Python面向对象编程
- 下一篇:14 - Python迭代器与生成器