第十章:面向对象基础
本章学习目标
- 理解类和对象的基本概念
- 掌握类的定义和实例化
- 理解封装、继承、多态三大特性
- 学会使用 dataclasses 简化类定义
- 理解特殊方法和运算符重载
10.1 类与对象基础
10.1.1 什么是面向对象
面向对象编程(OOP)是一种将数据和操作数据的方法封装在一起的编程范式。Python 是一门多范式语言,完全支持面向对象编程。
面向对象的核心概念:
- 类 (Class):对象的蓝图,定义对象的属性和方法
- 对象 (Object):类的实例,具有具体的数据
- 属性 (Attribute):类或对象的特征
- 方法 (Method):类中定义的函数
10.1.2 定义类
class Person:
"""人类"""
# 类属性(在所有实例间共享)
species: str = "Human"
# 实例属性(在 __init__ 中定义)
def __init__(self, name: str, age: int) -> None:
self.name: str = name
self.age: int = age
# 实例方法
def greet(self) -> str:
return f"Hello, I'm {self.name}"
def birthday(self) -> None:
self.age += 1
# 特殊方法
def __str__(self) -> str:
return f"Person(name={self.name}, age={self.age})"
def __repr__(self) -> str:
return f"Person(name={self.name!r}, age={self.age!r})"
10.1.3 创建和使用对象
# 创建实例
person = Person("Alice", 25)
# 访问实例属性
print(person.name) # Alice
print(person.age) # 25
# 访问类属性
print(person.species) # Human
print(Person.species) # Human
# 调用方法
print(person.greet()) # Hello, I'm Alice
# 修改实例属性
person.age = 26
person.birthday()
print(person.age) # 27
# 打印对象
print(person) # Person(name=Alice, age=27)
10.1.4 self 参数
self 指向对象实例本身,类似于其他语言中的 this:
class Calculator:
def add(self, a: float, b: float) -> float:
# self 指向调用此方法的对象
return a + b
# 调用时,Python 自动传递实例
calc = Calculator()
result = calc.add(3, 5) # 等价于 Calculator.add(calc, 3, 5)
10.2 封装
10.2.1 什么是封装
封装是将数据和操作封装在一起,并对外部隐藏内部实现细节。封装可以:
- 保护数据不被意外修改
- 提供清晰的接口
- 便于代码维护
10.2.2 访问控制
Python 使用命名约定来实现访问控制:
class BankAccount:
def __init__(self, account_id: str, balance: float = 0.0) -> None:
self.account_id: str = account_id # 公开
self._balance: float = balance # 受保护(约定)
self.__balance: float = balance # 私有(名称重整)
def get_balance(self) -> float:
"""公开的获取余额方法"""
return self._balance
def deposit(self, amount: float) -> None:
"""存款"""
if amount <= 0:
raise ValueError("存款金额必须为正数")
self._balance += amount
def withdraw(self, amount: float) -> None:
"""取款"""
if amount <= 0:
raise ValueError("取款金额必须为正数")
if amount > self._balance:
raise ValueError("余额不足")
self._balance -= amount
# 使用
account = BankAccount("001", 1000)
print(account.get_balance()) # 1000
account.deposit(500)
print(account.get_balance()) # 1500
account.withdraw(200)
print(account.get_balance()) # 1300
# 访问受保护属性(不推荐)
print(account._balance)
# 访问私有属性(会失败,Python 会重命名)
# print(account.__balance) # AttributeError
# 可以通过 _ClassName__attribute 访问
print(account._BankAccount__balance)
10.2.3 property 装饰器
使用 @property 实现属性的 getter、setter 和 deleter:
class Temperature:
"""温度类"""
def __init__(self, celsius: float = 0.0) -> None:
self._celsius: float = celsius
# getter
@property
def celsius(self) -> float:
return self._celsius
# setter
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
# 计算属性(华氏温度)
@property
def fahrenheit(self) -> float:
return self._celsius * 9 / 5 + 32
@fahrenheit.setter
def fahrenheit(self, value: float) -> None:
self._celsius = (value - 32) * 5 / 9
# deleter
@celsius.deleter
def celsius(self) -> None:
print("删除温度值")
self._celsius = 0
# 使用
temp = Temperature(25)
print(temp.celsius) # 25
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 100
print(temp.celsius) # 37.777...
del temp.celsius # 删除温度值
print(temp.celsius) # 0
10.3 继承
10.3.1 什么是继承
继承允许创建基于现有类的新类,新类(子类)继承父类的属性和方法。
class Animal:
"""动物基类"""
def __init__(self, name: str) -> None:
self.name = name
def speak(self) -> str:
return "..."
def __str__(self) -> str:
return f"Animal: {self.name}"
class Dog(Animal):
"""狗类"""
def speak(self) -> str:
return "Woof!"
class Cat(Animal):
"""猫类"""
def speak(self) -> str:
return "Meow!"
# 使用
dog = Dog("Buddy")
print(dog.name) # Buddy
print(dog.speak()) # Woof!
cat = Cat("Whiskers")
print(cat.speak()) # Meow!
# 多态
animals: list[Animal] = [Dog("Rex"), Cat("Luna"), Animal("Generic")]
for animal in animals:
print(f"{animal.name}: {animal.speak()}")
10.3.2 super() 函数
使用 super() 调用父类的方法:
class Person:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
class Employee(Person):
def __init__(self, name: str, age: int, employee_id: str) -> None:
# 调用父类的 __init__
super().__init__(name, age)
self.employee_id = employee_id
def __str__(self) -> str:
return f"{super().__str__()}, ID: {self.employee_id}"
# 使用
emp = Employee("Alice", 30, "E001")
print(emp.name) # Alice
print(emp.employee_id) # E001
print(emp) # Person(name='Alice', age=30), ID: E001
10.3.3 方法重写
子类可以重写父类的方法:
class Shape:
def area(self) -> float:
raise NotImplementedError("子类必须实现 area 方法")
def perimeter(self) -> float:
raise NotImplementedError("子类必须实现 perimeter 方法")
class Rectangle(Shape):
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
def perimeter(self) -> float:
import math
return 2 * math.pi * self.radius
10.3.4 多重继承
Python 支持多重继承,但需要注意方法解析顺序(MRO):
class Flyable:
def fly(self) -> str:
return "Flying..."
class Swimmable:
def swim(self) -> str:
return "Swimming..."
class Duck(Animal, Flyable, Swimmable):
def speak(self) -> str:
return "Quack!"
# 使用
duck = Duck("Donald")
print(duck.fly()) # Flying...
print(duck.swim()) # Swimming...
print(duck.speak()) # Quack!
# 查看方法解析顺序
print(Duck.__mro__)
10.3.5 抽象基类
使用 abc 模块定义抽象基类:
from abc import ABC, abstractmethod
class Shape(ABC):
"""抽象图形类"""
@abstractmethod
def area(self) -> float:
"""计算面积"""
pass
@abstractmethod
def perimeter(self) -> float:
"""计算周长"""
pass
# 可以有具体方法
def describe(self) -> str:
return f"A shape with area {self.area():.2f}"
class Rectangle(Shape):
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
# 不能直接实例化抽象类
# shape = Shape() # TypeError
rect = Rectangle(5, 3)
print(rect.area()) # 15
print(rect.perimeter()) # 16
print(rect.describe()) # A shape with area 15.00
10.4 多态
10.4.1 什么是多态
多态允许不同类的对象对同一消息作出不同的响应。
# 函数使用多态
def make_speak(animal: Animal) -> str:
return animal.speak()
dog = Dog("Rex")
cat = Cat("Luna")
print(make_speak(dog)) # Woof!
print(make_speak(cat)) # Meow!
10.4.2 运算符重载
通过特殊方法实现运算符重载:
class Vector:
"""二维向量"""
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Vector") -> "Vector":
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar: float) -> "Vector":
return self.__mul__(scalar)
def __neg__(self) -> "Vector":
return Vector(-self.x, -self.y)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __str__(self) -> str:
return f"({self.x}, {self.y})"
# 使用
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v1 - v2) # (-2, -2)
print(v1 * 3) # (3, 6)
print(3 * v1) # (3, 6)
print(-v1) # (-1, -2)
print(v1 == Vector(1, 2)) # True
10.4.3 完整运算符列表
| 方法 | 运算符 |
|---|---|
__add__ |
+ |
__sub__ |
- |
__mul__ |
* |
__truediv__ |
/ |
__floordiv__ |
// |
__mod__ |
% |
__pow__ |
** |
__and__ |
& |
__or__ |
| |
__xor__ |
^ |
__lt__, __le__, __gt__, __ge__, __eq__, __ne__ |
< <= > >= == != |
10.5 dataclasses (Python 3.7+)
10.5.1 基本使用
dataclasses 模块可以自动生成 __init__、__repr__、__eq__ 等方法:
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
email: str = ""
active: bool = True
# 自动生成 __init__, __repr__, __eq__
person = Person("Alice", 25)
print(person) # Person(name='Alice', age=25, email='', active=True)
# 支持默认值
person2 = Person("Bob", 30, "bob@example.com")
print(person2) # Person(name='Bob', age=30, email='bob@example.com', active=True)
# 比较
print(person == person2) # False
10.5.2 字段默认值
from dataclasses import dataclass, field
@dataclass
class User:
id: int
name: str
tags: list[str] = field(default_factory=list) # 使用 factory
created_at: str = field(default="")
def __post_init__(self) -> None:
"""初始化后处理"""
if not self.created_at:
from datetime import datetime
self.created_at = datetime.now().isoformat()
# 使用
user = User(1, "Alice")
print(user) # User(id=1, name='Alice', tags=[], created_at='2024-01-15T10:30:00')
user2 = User(2, "Bob", tags=["admin", "user"])
print(user2) # User(id=2, name='Bob', tags=['admin', 'user'], created_at='')
10.5.3 不可变 dataclass
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
def distance_from_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
# 不可变
p = Point(3, 4)
print(p) # Point(x=3, y=4)
print(p.distance_from_origin()) # 5.0
# 尝试修改会报错
# p.x = 5 # FrozenInstanceError
10.5.4 dataclass 进阶
from dataclasses import dataclass, field
from typing import ClassVar
@dataclass
class Rectangle:
width: float
height: float
# 类变量
count: ClassVar[int] = 0
def __post_init__(self) -> None:
Rectangle.count += 1
if self.width <= 0 or self.height <= 0:
raise ValueError("尺寸必须为正数")
@property
def area(self) -> float:
return self.width * self.height
@property
def perimeter(self) -> float:
return 2 * (self.width + self.height)
# 使用
r1 = Rectangle(5, 3)
r2 = Rectangle(4, 2)
print(r1.area) # 15
print(r2.perimeter) # 12
print(Rectangle.count) # 2
10.6 特殊方法
10.6.1 常用特殊方法
class MyList:
"""自定义列表类"""
def __init__(self, items: list[int]) -> None:
self.items = items
def __len__(self) -> int:
return len(self.items)
def __getitem__(self, index: int) -> int:
return self.items[index]
def __setitem__(self, index: int, value: int) -> None:
self.items[index] = value
def __contains__(self, item: int) -> bool:
return item in self.items
def __iter__(self):
return iter(self.items)
def __reversed__(self):
return reversed(self.items)
def __add__(self, other: "MyList") -> "MyList":
return MyList(self.items + other.items)
def __str__(self) -> str:
return f"MyList({self.items})"
# 使用
ml = MyList([1, 2, 3])
print(len(ml)) # 3
print(ml[0]) # 1
ml[0] = 10
print(ml) # MyList([10, 2, 3])
print(1 in ml) # False
print(2 in ml) # True
print(list(ml)) # [10, 2, 3]
print(list(reversed(ml))) # [3, 2, 10]
ml2 = MyList([4, 5])
ml3 = ml + ml2
print(ml3) # MyList([10, 2, 3, 4, 5])
10.6.2 callable 对象
class Counter:
"""可调用对象"""
def __init__(self, start: int = 0) -> None:
self.count = start
def __call__(self) -> int:
self.count += 1
return self.count
# 使用
counter = Counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
10.7 综合示例
示例 1:银行账户系统
from dataclasses import dataclass, field
from datetime import datetime
from typing import ClassVar
class InsufficientFundsError(Exception):
"""余额不足异常"""
pass
class InvalidAmountError(Exception):
"""无效金额异常"""
pass
@dataclass
class BankAccount:
"""银行账户"""
account_id: str
holder_name: str
_balance: float = 0.0
_transaction_count: ClassVar[int] = 0
def __post_init__(self) -> None:
if self._balance < 0:
raise ValueError("初始余额不能为负")
@property
def balance(self) -> float:
return self._balance
def deposit(self, amount: float) -> None:
"""存款"""
if amount <= 0:
raise InvalidAmountError("存款金额必须为正")
self._balance += amount
BankAccount._transaction_count += 1
def withdraw(self, amount: float) -> None:
"""取款"""
if amount <= 0:
raise InvalidAmountError("取款金额必须为正")
if amount > self._balance:
raise InsufficientFundsError(
f"余额不足: 余额 {self._balance}, 尝试取款 {amount}"
)
self._balance -= amount
BankAccount._transaction_count += 1
def transfer(self, target: "BankAccount", amount: float) -> None:
"""转账"""
self.withdraw(amount)
target.deposit(amount)
def __str__(self) -> str:
return f"Account({self.account_id}, {self.holder_name}, ${self._balance:.2f})"
# 使用
account1 = BankAccount("001", "Alice", 1000)
account2 = BankAccount("002", "Bob", 500)
print(account1) # Account(001, Alice, $1000.00)
account1.deposit(500)
print(account1.balance) # 1500
account1.withdraw(200)
print(account1.balance) # 1300
account1.transfer(account2, 300)
print(account1.balance) # 1000
print(account2.balance) # 800
print(f"总交易次数: {BankAccount._transaction_count}") # 5
# 异常处理
try:
account1.withdraw(2000)
except InsufficientFundsError as e:
print(f"错误: {e}")
示例 2:形状系统
from abc import ABC, abstractmethod
from dataclasses import dataclass
import math
class Shape(ABC):
"""抽象图形基类"""
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
def describe(self) -> str:
return f"{self.__class__.__name__}: 面积={self.area():.2f}, 周长={self.perimeter():.2f}"
@dataclass
class Rectangle(Shape):
width: float
height: float
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
@dataclass
class Circle(Shape):
radius: float
def area(self) -> float:
return math.pi * self.radius ** 2
def perimeter(self) -> float:
return 2 * math.pi * self.radius
@dataclass
class Triangle(Shape):
a: float
b: float
c: float
def area(self) -> float:
# 海伦公式
s = self.perimeter() / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self) -> float:
return self.a + self.b + self.c
# 使用
shapes: list[Shape] = [
Rectangle(5, 3),
Circle(2),
Triangle(3, 4, 5)
]
total_area = 0.0
for shape in shapes:
print(shape.describe())
total_area += shape.area()
print(f"\n总面积: {total_area:.2f}")
最佳实践
- 优先使用 dataclasses:简化数据类的创建
# ✗ 不推荐
class Person:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
# ✓ 推荐
@dataclass
class Person:
name: str
age: int
使用抽象基类定义接口:明确类的职责
遵循单一职责原则:每个类只负责一件事
使用属性而不是直接访问:提供更好的封装
避免多重继承:增加复杂性
课后练习
练习 10.1:Rectangle 类
创建一个 Rectangle 类,包含:
- width 和 height 属性
- area() 和 perimeter() 方法
- 使用 property 实现 getter/setter
练习 10.2:继承实现动物类
创建 Animal 基类和 Dog、Cat 子类,实现 speak() 方法。
练习 10.3:使用 dataclass
使用 dataclass 创建 Student 类,包含 id、name、age、grades 属性。
练习 10.4:运算符重载
创建一个复数类 Complex,支持 +、-、*、/ 运算。
练习 10.5:抽象基类
创建一个抽象基类 Stack,实现 push、pop、is_empty 方法。
练习 10.6:银行账户系统
创建一个完整的银行账户系统,支持存款、取款、转账、查询余额。
本章小结
本章我们详细学习了 Python 面向对象编程:
类和对象:
- 类的定义和实例化
- 属性和方法
- self 参数
封装:
- 访问控制(受保护、私有)
- property 装饰器
继承:
- 单继承和多重继承
- super() 函数
- 方法重写
多态:
- 运算符重载
- 抽象基类
dataclasses:
- 简化数据类
- 不可变 dataclass
特殊方法:
- 常用特殊方法
- callable 对象
面向对象编程是 Python 开发大型应用的基础,需要多加练习。