09 - 面向对象编程
第 09 章:面向对象编程
掌握 Python 的类、继承、多态、魔术方法和高级面向对象特性。
9.1 类基础
9.1.1 定义与实例化
class Dog:
"""一只狗。"""
# 类变量(所有实例共享)
species = "Canis lupus familiaris"
def __init__(self, name: str, breed: str, age: int):
"""初始化实例。"""
# 实例变量(每个实例独有)
self.name = name
self.breed = breed
self.age = age
def bark(self) -> str:
"""叫。"""
return f"{self.name}: 汪汪!"
def __repr__(self) -> str:
return f"Dog(name={self.name!r}, breed={self.breed!r}, age={self.age})"
# 创建实例
rex = Dog("Rex", "德国牧羊犬", 5)
buddy = Dog("Buddy", "金毛", 3)
print(rex.bark()) # Rex: 汪汪!
print(rex.species) # Canis lupus familiaris
print(Dog.species) # Canis lupus familiaris
9.1.2 实例方法、类方法和静态方法
class Circle:
PI = 3.14159
def __init__(self, radius: float):
self.radius = radius
# 实例方法:第一个参数是 self
def area(self) -> float:
return Circle.PI * self.radius ** 2
# 类方法:第一个参数是 cls
@classmethod
def from_diameter(cls, diameter: float) -> "Circle":
return cls(diameter / 2)
# 静态方法:没有特殊参数
@staticmethod
def is_valid_radius(radius: float) -> bool:
return radius > 0
# 使用
c1 = Circle(5)
print(c1.area()) # 78.53975
c2 = Circle.from_diameter(10) # 类方法创建
print(c2.radius) # 5.0
print(Circle.is_valid_radius(-1)) # False
9.2 继承
9.2.1 基本继承
class Animal:
def __init__(self, name: str, sound: str):
self.name = name
self.sound = sound
def speak(self) -> str:
return f"{self.name} says {self.sound}"
class Dog(Animal):
def __init__(self, name: str, breed: str):
super().__init__(name, "Woof")
self.breed = breed
def fetch(self, item: str) -> str:
return f"{self.name} fetches the {item}"
class Cat(Animal):
def __init__(self, name: str, indoor: bool = True):
super().__init__(name, "Meow")
self.indoor = indoor
# 使用
dog = Dog("Rex", "German Shepherd")
cat = Cat("Whiskers")
print(dog.speak()) # Rex says Woof
print(dog.fetch("ball")) # Rex fetches the ball
print(cat.speak()) # Whiskers says Meow
9.2.2 多继承与 MRO
class Flyable:
def fly(self) -> str:
return f"{self.name} is flying"
class Swimmable:
def swim(self) -> str:
return f"{self.name} is swimming"
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name: str):
super().__init__(name, "Quack")
donald = Duck("Donald")
print(donald.speak()) # Donald says Quack
print(donald.fly()) # Donald is flying
print(donald.swim()) # Donald is swimming
# 方法解析顺序(MRO)
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Flyable'>, <class 'Swimmable'>, <class 'object'>)
9.2.3 isinstance 和 issubclass
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(isinstance(dog, Cat)) # False
print(issubclass(Dog, Animal)) # True
print(issubclass(Dog, object)) # True
9.3 多态
class Shape:
def area(self) -> float:
raise NotImplementedError
def perimeter(self) -> float:
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, width: float, height: float):
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):
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
# 多态:统一接口处理不同类型
def print_shape_info(shape: Shape) -> None:
print(f"面积: {shape.area():.2f}")
print(f"周长: {shape.perimeter():.2f}")
print_shape_info(Rectangle(5, 3))
print_shape_info(Circle(4))
9.4 魔术方法(Dunder Methods)
9.4.1 常用魔术方法
| 方法 | 触发方式 | 说明 |
|---|---|---|
__init__ | obj = Cls() | 初始化 |
__repr__ | repr(obj) | 开发者友好表示 |
__str__ | str(obj), print(obj) | 用户友好表示 |
__len__ | len(obj) | 长度 |
__getitem__ | obj[key] | 索引访问 |
__setitem__ | obj[key] = value | 索引赋值 |
__delitem__ | del obj[key] | 索引删除 |
__contains__ | x in obj | 成员检查 |
__iter__ | for x in obj | 迭代 |
__eq__ | obj1 == obj2 | 等于 |
__lt__ | obj1 < obj2 | 小于 |
__add__ | obj1 + obj2 | 加法 |
__call__ | obj() | 可调用 |
__enter__ | with obj | 上下文入口 |
__exit__ | with obj | 上下文出口 |
9.4.2 示例:自定义容器
class SortedList:
"""自动排序的列表。"""
def __init__(self, items: list[int] | None = None):
self._data = sorted(items or [])
def __repr__(self) -> str:
return f"SortedList({self._data})"
def __len__(self) -> int:
return len(self._data)
def __getitem__(self, index: int) -> int:
return self._data[index]
def __contains__(self, item: int) -> bool:
return item in self._data
def __iter__(self):
return iter(self._data)
def __eq__(self, other: object) -> bool:
if not isinstance(other, SortedList):
return NotImplemented
return self._data == other._data
def add(self, item: int) -> None:
"""插入元素并保持排序。"""
import bisect
bisect.insort(self._data, item)
# 使用
sl = SortedList([3, 1, 4, 1, 5])
sl.add(2)
print(sl) # SortedList([1, 1, 2, 3, 4, 5])
print(len(sl)) # 6
print(3 in sl) # True
print(sl[0]) # 1
for item in sl:
print(item, end=" ") # 1 1 2 3 4 5
9.4.3 可调用对象
class Multiplier:
"""可调用的乘法器。"""
def __init__(self, factor: int):
self.factor = factor
def __call__(self, x: int) -> int:
return x * self.factor
def __repr__(self) -> str:
return f"Multiplier(factor={self.factor})"
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# 可调用对象可以用在任何需要函数的地方
numbers = [1, 2, 3, 4, 5]
print(list(map(double, numbers))) # [2, 4, 6, 8, 10]
9.5 属性控制
9.5.1 property
class Temperature:
def __init__(self, celsius: float):
self._celsius = celsius
@property
def celsius(self) -> float:
return self._celsius
@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
# 使用
t = Temperature(100)
print(t.celsius) # 100
print(t.fahrenheit) # 212.0
t.fahrenheit = 32
print(t.celsius) # 0.0
# t.celsius = -300 # ValueError: 温度不能低于绝对零度
9.5.2 __slots__
class Point:
__slots__ = ("x", "y") # 限制属性,减少内存
def __init__(self, x: float, y: float):
self.x = x
self.y = y
p = Point(3, 4)
print(p.x, p.y) # 3 4
# p.z = 5 # AttributeError: 'Point' object has no attribute 'z'
9.6 抽象类(ABC)
from abc import ABC, abstractmethod
class Repository(ABC):
"""数据仓库抽象基类。"""
@abstractmethod
def get(self, id: int) -> dict:
"""获取单条记录。"""
...
@abstractmethod
def save(self, record: dict) -> None:
"""保存记录。"""
...
@abstractmethod
def delete(self, id: int) -> None:
"""删除记录。"""
...
def exists(self, id: int) -> bool:
"""检查记录是否存在(可选覆盖)。"""
try:
self.get(id)
return True
except KeyError:
return False
class MemoryRepository(Repository):
"""内存仓库实现。"""
def __init__(self):
self._data: dict[int, dict] = {}
self._next_id = 1
def get(self, id: int) -> dict:
if id not in self._data:
raise KeyError(f"记录 {id} 不存在")
return self._data[id]
def save(self, record: dict) -> None:
record.setdefault("id", self._next_id)
self._data[record["id"]] = record
self._next_id = max(self._next_id, record["id"] + 1)
def delete(self, id: int) -> None:
if id not in self._data:
raise KeyError(f"记录 {id} 不存在")
del self._data[id]
# repo = Repository() # ❌ TypeError: Can't instantiate abstract class
repo = MemoryRepository()
repo.save({"name": "Alice"})
print(repo.get(1)) # {'name': 'Alice', 'id': 1}
print(repo.exists(1)) # True
9.7 Protocol(Python 3.8+)
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
"""可绘制协议。"""
def draw(self) -> str: ...
class Square:
"""不需要显式继承 Drawable。"""
def __init__(self, side: float):
self.side = side
def draw(self) -> str:
return f"绘制边长为 {self.side} 的正方形"
class Button:
"""按钮类。"""
def __init__(self, label: str):
self.label = label
def draw(self) -> str:
return f"绘制按钮: {self.label}"
def render(item: Drawable) -> None:
"""渲染任何可绘制对象。"""
print(item.draw())
# 结构化子类型:只要实现了 draw() 就满足 Drawable 协议
render(Square(5)) # 绘制边长为 5 的正方形
render(Button("确定")) # 绘制按钮: 确定
# 运行时类型检查
print(isinstance(Square(5), Drawable)) # True
print(isinstance(Button("OK"), Drawable)) # True
Protocol vs ABC 对比
| 特性 | ABC | Protocol |
|---|---|---|
| 继承要求 | 必须显式继承 | 不需要继承 |
| 类型检查 | isinstance() | isinstance()(需 @runtime_checkable) |
| 类型系统 | nominal | structural |
| 推荐场景 | 有共同实现的层次 | 鸭子类型的形式化 |
9.8 组合 vs 继承
# 组合(推荐)
class Engine:
def start(self) -> str:
return "引擎启动"
class Car:
def __init__(self, engine: Engine):
self.engine = engine # 组合:has-a 关系
def start(self) -> str:
return self.engine.start()
# 继承
class ElectricEngine(Engine):
def start(self) -> str:
return "电动引擎静默启动"
class ElectricCar(Car):
pass
car = Car(Engine())
e_car = ElectricCar(ElectricEngine())
print(car.start()) # 引擎启动
print(e_car.start()) # 电动引擎静默启动
9.9 注意事项
🔴 注意:
- 所有方法的第一个参数必须是
self(实例方法)或cls(类方法) __repr__面向开发者,__str__面向用户,优先实现__repr__- 不要过度使用继承,优先使用组合
- 多继承容易导致菱形问题,使用时注意 MRO
💡 提示:
- 使用
@dataclass替代简单的数据类 - 使用
Protocol实现鸭子类型的形式化 property用于需要验证或计算的属性__slots__可以减少大量实例的内存开销
📌 业务场景:
from dataclasses import dataclass, field
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
"""支付网关抽象基类。"""
@abstractmethod
def charge(self, amount: float, currency: str = "CNY") -> dict: ...
@abstractmethod
def refund(self, transaction_id: str) -> dict: ...
@dataclass
class AlipayGateway(PaymentGateway):
"""支付宝网关。"""
app_id: str
def charge(self, amount: float, currency: str = "CNY") -> dict:
# 实际实现调用支付宝 API
return {"status": "success", "amount": amount, "gateway": "alipay"}
def refund(self, transaction_id: str) -> dict:
return {"status": "refunded", "transaction_id": transaction_id}
@dataclass
class WechatPayGateway(PaymentGateway):
"""微信支付网关。"""
mch_id: str
def charge(self, amount: float, currency: str = "CNY") -> dict:
return {"status": "success", "amount": amount, "gateway": "wechat"}
def refund(self, transaction_id: str) -> dict:
return {"status": "refunded", "transaction_id": transaction_id}
# 多态使用
def process_payment(gateway: PaymentGateway, amount: float) -> dict:
return gateway.charge(amount)
alipay = AlipayGateway(app_id="2021000000000")
wechat = WechatPayGateway(mch_id="1234567890")
print(process_payment(alipay, 99.9)) # {'status': 'success', ...}
print(process_payment(wechat, 99.9)) # {'status': 'success', ...}
9.10 扩展阅读
- Classes - Python 文档
- PEP 544 - Protocols
- Data model - Python 文档
- abc 模块
- 《流畅的 Python》第 10-13 章