
简介这份资源是一份面向 Python 学习者与开发者的面向对象设计源码合集围绕类、对象、继承、封装、多态和常见设计模式展开适合自学、备课和面试复习。压缩包共 43 个文件包括 21 个 Python 示例源码、21 个 Markdown 讲解笔记和 1 个说明文档整体仅 36KB轻量易用。源码覆盖类的创建、构造函数、类属性与实例属性、单类/多类继承、普通/静态/类方法、特性方法、str与new等魔术方法并给出工厂设计模式和登录案例实战配套笔记拆解各知识点补充数据隐藏、常用魔术方法并延伸到 UI 自动化场景。当前已有 420 人学习使用目录按 day08 组织文件名清晰便于逐节对照阅读。通过这套源码和笔记读者能理清 OOP 的实现原理学会把设计模式应用到真实项目中从而写出结构更清晰、更易维护的 Python 代码。1. 面向对象详细设计从设计文档到Python源码先定义清楚类的边界做软件工程的都知道概要设计定模块详细设计定类和接口。但很多团队的详细设计文档交付后代码却和文档各写各的——类图画了十几个框落进Python里却成了一堆函数加字典。这不是执行力问题而是设计语言和实现语言之间缺了一座桥。面向对象详细设计真正要回答的问题是类之间的依赖朝哪个方向走、哪些东西只能在初始化时定下来、哪些行为必须由子类覆盖、状态迁移的合法性由谁保证。源码不是类图的翻译而是把设计里没画出来的约束用代码显式表达出来。这篇讲的就是怎么把一份详细设计落成结构清楚、能跑、能测的Python源码适合正在写详细设计或拿到设计文档不知道该从哪个文件动手的开发者。对写过几年Python的人重点在后半部分接口抽象、状态机落地和验证方法。2. 设计类图到Python代码字段、关联与依赖直译的正确姿势2.1 把类图里的字段翻译成数据类先解决字段从哪来详细设计文档里每个类都会给出属性表常见格式是属性名 类型 可见性 初始值。落到Python里最直接的映射对象是dataclasses.dataclass而不是手写__init__。原因有三条字段列表和设计文档一一对应方便评审提供默认值和field(default_factory...)的机制能表达可变默认值自动生成的__repr__和__eq__在调试和单测断言时有实际价值。订单明细 —— 对应详细设计文档中的 OrderItem 类 from dataclasses import dataclass, field from decimal import Decimal from typing import Optional dataclass class OrderItem: item_id: str product_code: str quantity: int unit_price: Decimal remark: Optional[str] None # 折扣率0.9 表示九折默认空列表避免可变默认值陷阱 discount_snapshots: list field(default_factorylist) def subtotal(self) - Decimal: 未折扣小计金额一律用 Decimal 避免浮点误差 return self.unit_price * self.quantity这段代码背后的设计意图是把详细设计里折扣快照这个关联关系建模成独立的列表字段而不是在Order里临时拼字典。field(default_factorylist)解决的是Python可变默认值的经典问题——如果直接写discount_snapshots: list []所有OrderItem实例会共享同一个列表后面往里追加数据时其他实例的数据会一起变。写设计文档的人不会管这个但写源码的人必须在翻译过程中把这个坑堵上。Optional[str]对应文档里的可空备注字段Decimal替代float是金额字段的铁律。subtotal方法对应详细设计里的派生属性——文档里可能画的是/小计这样的斜杠标记表示不落库、由计算得出在源码里就是方法而不是字段。2.2 处理类之间的关联组合用类型标注依赖注入放构造器详细设计里的类关系通常画成关联线和依赖虚线。关联线表示持有关系依赖线表示临时用到。这两者在Python代码里的实现方式完全不同。持有一个对象并且整个生命周期都要用就放进构造器参数用类型标注声明临时用到某个对象就作为方法参数传入而不是存在实例上。订单聚合根对应文档中与 OrderItem 的聚合关系 from dataclasses import dataclass, field from datetime import datetime from typing import List, Optional dataclass class Order: order_id: str customer_id: str created_at: datetime items: List[OrderItem] field(default_factorylist) status: str CREATED # 状态字段后续章节用状态机接管 def add_item(self, item: OrderItem) - None: 订单添加明细状态未支付时允许操作 if self.status ! CREATED: raise RuntimeError(f订单当前状态为 {self.status}不允许添加明细) self.items.append(item) self.status PENDING def total_amount(self) - Decimal: 汇总金额对应详细设计里的聚合计算逻辑 return sum((i.subtotal() for i in self.items), Decimal(0))注意List[OrderItem]这里用了字符串形式的注解。因为Order和OrderItem相互引用在模块加载顺序上可能产生前置引用问题字符串注解配合from __future__ import annotations可以在不破坏代码顺序的前提下完成类型检查。构造器里不放具体对象而是放依赖的抽象是详细设计从画对关系到写对代码的关键一步——后面第3章会展开讲如何用抽象基类和协议来组织依赖方向。2.3 参数表从设计文档到代码的字段映射该检查什么设计文档元素Python映射方式容易踩的坑基本类型属性str/int/bool直接标注金额和百分比用float会丢精度日期时间字段datetime/date类型直接用字符串存排序和计算都别扭文件、图片等大对象存路径或唯一标识不直接持有把File对象放进字段序列化失败一对一/一对多关联类型标注 构造器参数忽略可达性导致对象图循环引用派生属性定义为方法而非字段初始化时就算后续依赖数据变了还算旧值默认可变集合field(default_factory...)直接 []导致共享状态污染这张表是代码评审时的对照清单。详细设计文档里每个类的属性表都要能在这张表里找到对应关系。找不到的要么是文档写得不够细要么是代码里用了文档以外的隐性设计——两种情况都该停下来对齐。3. 接口与组合先行用抽象基类约束行为替后续扩展留接口3.1 区分两种接口设计ABC管契约Protocol管结构详细设计里接口这个词很重但Python没有Java那种独立的interface关键字所以需要先明确设计文档里的接口到底是要约束实现类的行为还是要约束调用方传入的对象结构。需要被继承并实现完整行为的用abc.ABC加抽象方法子类必须补全所有未实现的方法只要求传进来的对象有某个方法而不关心它继承自谁的用typing.Protocol配合runtime_checkable做运行时结构校验。选错导致的问题通常是用了ABC把调用方的对象也必须塞进继承体系里或者用了Protocol却约束不了必须重写的语义。仓储接口对应详细设计里的 OrderRepository 边界类 from abc import ABC, abstractmethod class OrderRepository(ABC): abstractmethod def save(self, order: Order) - None: 持久化订单由基础设施层实现 abstractmethod def find_by_id(self, order_id: str) - Order | None: 按主键查询订单查不到返回 None这个接口的意义不在能跑而在依赖倒置——详细设计里订单服务依赖OrderRepository而不是依赖MySqlOrderRepository。实现方在独立模块里写MySqlOrderRepository(OrderRepository)数据库换成别的时服务层代码不需要动。ABC在这里强制了实现方必须提供save和find_by_id少一个类就无法实例化这在多人协作时能让设计约束在编译期Python里是导入期就暴露出来。3.2 库存扣减策略把设计里的策略类变成组合对象详细设计里经常出现可选择多种算法的场景比如促销折扣、库存扣减策略。面向对象的方式不是让订单类里写满if promotion_type FULL_REDUCTION而是把算法独立成类运行时组合给订单。这种策略模式在详细设计里画的是订单 ——依赖-- 折扣策略的虚线依赖属于调用关系而非持有关系。折扣策略族详细设计里的 Strategy 或 Policy 类的落地 from abc import ABC, abstractmethod from decimal import Decimal class DiscountPolicy(ABC): abstractmethod def apply(self, item: OrderItem) - Decimal: 输入订单明细返回折扣后的单价/折扣金额 class NoDiscount(DiscountPolicy): def apply(self, item: OrderItem) - Decimal: return item.unit_price class PercentDiscount(DiscountPolicy): 按百分比折扣percent90 表示打九折 def __init__(self, percent: Decimal) - None: self.percent percent # 90 表示 90% 价格 def apply(self, item: OrderItem) - Decimal: return item.unit_price * self.percent / Decimal(100)这里关键的不是子类怎么写而是DiscountPolicy为什么必须是抽象类而不是普通类。普通类也能被继承和改写但abstractmethod明确表达了一个设计事实DiscountPolicy自身没有合法实现它的意义只在约束子类。这一点在评审详细设计和源码一致性时很管用——实现代码的人能一眼看出哪些类是设计里不允许实例化的。3.3 接口参数怎么定方法签名里的三个黄金问题写接口方法时每次都要问自己三个问题。第一这个方法的输入参数来自设计文档的哪个对象参数数量超过三个时考虑收敛成一个值对象比如QueryCriteria而不是继续往方法里加参数。第二返回值是可选的吗可空返回必须显式写出Optional[...]或... | None免得调用方忽略None导致AttributeError。第三这个方法会有副作用吗持久化、发消息、改状态这类副作用要体现在方法名里比如save和mark_as_paid不要叫process这种看不出副作用的名字。查询条件值对象把三个以上的查询参数收拢成一个对象 from dataclasses import dataclass from datetime import datetime from typing import Optional dataclass(frozenTrue) class OrderQuery: customer_id: Optional[str] None status_in: Optional[list[str]] None created_after: Optional[datetime] None created_before: Optional[datetime] None def find_orders(self, query: OrderQuery) - list[Order]: 按条件查询订单。query 内字段为 None 表示不过滤。 # 实际实现交给具体仓储 ...参数对象化看似多写了类型但换来的是接口的稳定。新增查询条件时OrderQuery加字段原有调用方不需要改方法签名而如果直接在find_orders方法上加参数所有调用点都要跟着变。frozenTrue让这个查询对象不可变防止查询过程中被意外修改导致查询条件漂移。设计文档里如果画了查询条件类源码里对应的就是这个。4. 时序与状态落地把方法调用链和状态迁移写成可测试代码4.1 从时序图到方法调用谁发起、谁响应、谁落库详细设计里的时序图反映的是运行时的协作顺序比如创建订单 - 校验库存 - 扣减库存 - 保存订单。这部分翻译成源码时最容易犯的错是把流程逻辑写进控制器层让 HTTP 层堆积业务逻辑。常规做法是提取一个应用服务Application Service来编排协作对象订单服务去调用仓储接口和库存服务而不是让路由处理器去逐行操作Order。订单应用服务对应详细设计里订单模块的时序流程 class OrderService: def __init__(self, order_repo: OrderRepository, inventory_service) - None: self._order_repo order_repo self._inventory inventory_service def create_order(self, order: Order) - None: 创建订单的对外入口校验 - 扣库存 - 保存 # 1. 校验订单明细是否齐全 if not order.items: raise ValueError(订单至少需要一条明细) # 2. 锁库存由库存服务判断并扣减 self._inventory.reserve(order) # 3. 持久化订单此时订单状态为 PENDING self._order_repo.save(order)把流程编排放在服务层而不是订单聚合根里是有意的设计选择。Order只负责自身状态和内部一致性OrderService负责跨对象的协作编排。这样做的好处是单元测试可以分别测测试OrderService时用假的order_repo和假的inventory_service不需要真的数据库和库存服务测试Order时直接构造实例调用方法不需要经过 HTTP。4.2 状态迁移用状态机enum约束值域迁移表约束合法性详细设计里的状态图是硬约束哪些状态能迁到哪些状态、哪些操作触发迁移。用字符串存状态是详细设计源码里最常见的败笔——PAID 拼成 PAIDD 要等到线上才能发现。用enum.Enum限定状态值域再用一个显式的迁移表控制并发和非法操作。订单状态枚举约束状态值的合法范围 from enum import Enum class OrderStatus(Enum): CREATED CREATED # 已创建未提交 PENDING PENDING # 已提交待支付 PAID PAID # 已支付 CANCELLED CANCELLED # 已取消 CLOSED CLOSED # 已关闭 # 状态迁移表key 为当前状态value 为允许的下一状态集合 _ALLOWED_TRANSITIONS { OrderStatus.CREATED: {OrderStatus.PENDING, OrderStatus.CANCELLED}, OrderStatus.PENDING: {OrderStatus.PAID, OrderStatus.CANCELLED}, OrderStatus.PAID: {OrderStatus.CLOSED}, OrderStatus.CANCELLED: set(), OrderStatus.CLOSED: set(), } def can_transit(current: OrderStatus, target: OrderStatus) - bool: 判断状态迁移是否合法 return target in _ALLOWED_TRANSITIONS[current]Order里的status字段从字符串改为枚举后所有比较逻辑都需要同步调整if order.status OrderStatus.PAID而不是 PAID。枚举带来的直接收益是IDE的类型推断和静态检查能发现拼写错误。状态迁移表把允许谁迁移到谁集中在一个写死的数据结构里比散落在各个方法里的if判断更容易审查和测试。要测非法迁移只需要assert not can_transit(OrderStatus.PAID, OrderStatus.CANCELLED)这样一行。4.3 状态迁移要串到业务流程里不能只画表不落地上面的can_transit是纯函数它不会阻止代码里的非法迁移。真实的流程里状态修改必须经过一个统一入口from dataclasses import dataclass dataclass class Order: # ... 其他字段省略 ... status: OrderStatus OrderStatus.CREATED def transit(self, target: OrderStatus) - None: 迁移订单状态。非法迁移直接抛异常迁移成功时打印审计日志。 if not can_transit(self.status, target): raise ValueError( f非法状态迁移{self.status.value} - {target.value} ) old_status self.status self.status target # 记录状态变更审计信息实际项目里可对接事件总线 print(f状态变更: {old_status.value} - {target.value})有人会觉得这是小题大做直接order.status OrderStatus.PAID就好了。但详细设计里画了状态图就等于承诺了这些状态约束必须在代码里生效。如果不把迁移逻辑收拢到transit方法状态约束就只是一张挂在文档里的图。这里的print在实际工程化时应该替换成日志对象或者审计事件的发布详细设计里如果定义了事件通知机制就在这里发事件。4.4 细节设计状态机与策略模式联动的完整示例把前面几个部分拼起来可以看到一个相对完整的订单处理链路。创建订单后进入PENDING此时应用服务根据策略计算金额并尝试扣库存class OrderService: def __init__( self, order_repo: OrderRepository, inventory_service, policy: DiscountPolicy, ) - None: self._order_repo order_repo self._inventory inventory_service self._policy policy def _calculate(self, order: Order) - Decimal: 计算订单总价应用当前折扣策略 total Decimal(0) for item in order.items: total self._policy.apply(item) return total def place(self, order: Order) - Decimal: 提交订单锁定库存并计算应支付金额 order.transit(OrderStatus.PENDING) self._inventory.reserve(order) amount self._calculate(order) self._order_repo.save(order) return amount这个流程里transit在操作序列的最前面执行先于reserve和save——这是有意的顺序状态迁移失败时后续动作不会执行避免库存锁了但状态没改的不一致场景。如果把transit放到最后reserve抛异常时订单状态还是CREATED但库存已经被扣了要回滚就更费劲。这也是详细设计时序图里隐含的事务边界——谁先做谁后做对应着异常发生时状态怎么回滚。写源码时把这一条当作默认规则校验类操作在前状态变更居中落库和外部调用放最后。5. 详细设计源码的验证用结构和测试反向校对设计遗漏5.1 用inspect和元类自动验证接口实现完整性详细设计里定义的接口实现类有没有漏实现哪个方法人工翻文件效率低写一段结构测试脚本来自动检查更可靠。利用inspect模块扫描所有抽象方法逐一在当前模块里查对应实现。结构校验脚本检查所有接口实现类是否完整实现抽象方法 import inspect from abc import ABC, abstractmethod def is_abstract_method(member) - bool: return isinstance(member, abstractmethod) or ( inspect.isfunction(member) and getattr(member, __isabstractmethod__, False) ) def validate_interfaces(module) - list[str]: 遍历模块内所有类返回未完整实现接口的类名 problems [] for _, cls in inspect.getmembers(module, inspect.isclass): if not cls.__module__ module.__name__: continue # 找出类本身声明的抽象方法 abstract_methods set() for base in cls.__mro__: for name, member in vars(base).items(): if is_abstract_method(member): abstract_methods.add(name) # 在当前类中没有对应的具体实现 -- 仍为抽象类则跳过 if inspect.isabstract(cls): continue # 有抽象方法未实现 missing abstract_methods - set(cls.__dict__.keys()) if missing: problems.append(f{cls.__name__} 未实现: {missing}) return problems if __name__ __main__: import order_models # 示例模块按实际替换 for issue in validate_interfaces(order_models): print(issue)这段脚本会比较所有抽象基类的抽象方法清单和当前类自身实现的成员。inspect.isabstract(cls)判断该类是否仍处于抽象状态如果已经是具体类但还有缺失的抽象方法就会被列入问题清单。把这个脚本作为CI的一步能拦住设计定了接口但实现类没跟上的情况相当于把详细设计文档里的接口契约自动化了。5.2 单元测试只测公开契约不测实现细节详细设计源码的测试应该对准对外行为而不是内部实现。拿Order的状态迁移来说测试关注的是正常路径能迁移、非法路径抛异常、迁移后状态正确。不关心状态是不是存在枚举里也不关心transit内部用了几行代码实现。订单状态迁移的单元测试 import pytest from order_models import Order, OrderStatus def test_pending_to_paid_is_allowed(): order Order(order_idA001, customer_idC001) order.transit(OrderStatus.PENDING) order.transit(OrderStatus.PAID) assert order.status OrderStatus.PAID def test_paid_to_cancelled_is_forbidden(): order Order(order_idA002, customer_idC002) order.transit(OrderStatus.PENDING) order.transit(OrderStatus.PAID) with pytest.raises(ValueError): order.transit(OrderStatus.CANCELLED)测试方法名的test_前缀是pytest 的默认收集规则assert order.status OrderStatus.PAID直接校验公开状态。第二个用例故意走一个非法迁移路径断言ValueError被抛出。这两个用例覆盖了状态机最关键的两个面合法路径放行、非法路径拒绝。配合_ALLOWED_TRANSITIONS表任何新增的状态转移都能用同样的模式补一个测试。5.3 反向检查设计遗漏的字段有没有在代码里临时长出来详细设计评审时常见的一个场景是源码里有个字段但文档里没有。这通常是设计变更没有回写到设计文档导致的。反过来源码里不一定需要单独验证每个字段都在文档里但可以写个简单的枚举脚本检查模块里 dataclass 的字段清单然后和设计文档导出的清单对比差异。输出模块内 dataclass 的字段清单用于与设计文档对比 from dataclasses import fields, is_dataclass import order_models for name, obj in vars(order_models).items(): if is_dataclass(obj): field_names [f.name for f in fields(obj)] print(f{name}: {, .join(field_names)})这个脚本在生产环境通常不开只在设计评审前跑。它解决的是文档和代码谁先走偏的问题。脚本输出的字段清单可以直接贴在评审记录里和设计文档的类属性表逐项对照。双方都基于同一份清单对齐比人工翻代码要不遗漏得多。5.4 最后一招用__subclasses__()反向注册策略类策略模式落地后新加一个折扣策略需要修改调用方吗不需要。利用DiscountPolicy的子类自动注册机制可以按名字查找策略实现避免改动OrderService。策略注册表通过子类自动发现实现 def get_policy_by_name(name: str) - type[DiscountPolicy] | None: 按策略名查找子类。未找到返回 None。 for sub in DiscountPolicy.__subclasses__(): if sub.__name__.lower() name.lower(): return sub return None # 使用示例 policy_cls get_policy_by_name(PercentDiscount) policy policy_cls(percentDecimal(85))__subclasses__()只能找到直接继承的子类不足以覆盖多层继承但对折扣策略这类一层继承的场景足够了。这段代码的工程价值在于以后新增满减策略时只需要写一个FullReductionDiscount(DiscountPolicy)不需要改动OrderService的构造逻辑。策略注册表把开闭原则从设计文档里落到实处——新增行为不修改已有代码。对接到前面的测试结构里策略类有没有被注册、方法有没有实现都能用脚本自动验证不需要手动检查每一处调用。本文还有配套的精品资源点击获取