强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 对比

特性ABCProtocol
继承要求必须显式继承不需要继承
类型检查isinstance()isinstance()(需 @runtime_checkable
类型系统nominalstructural
推荐场景有共同实现的层次鸭子类型的形式化

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 扩展阅读