Python模块化实战:从一团乱麻到井然有序的代码架构
我记得很清楚第一次被Python项目“折磨”到怀疑人生是一个爬虫项目。前两周很爽写一个文件就能跑起来。第三周需求开始叠加文件堆到2000多行我每次改一个函数就得全局搜索它在哪里被调用改完还会带出新的报错最后连自己都分不清某个变量到底在哪个阶段被改了值。从那时候起我开始认真对待一件事——Python模块化。这篇内容的核心就是“Python模块”。但它不是概念罗列而是把我这些年把代码从“一团乱麻”整理成“井然有序”的过程拆成几个能直接落地的环节来讲模块和包的机制、import背后的原理、模块的创建方法、真实项目里的拆分策略以及那些我踩过的坑。无论你是刚学Python不久还是已经写了几个项目正打算重构都应该能从里面找到有用的东西。1. 代码变乱不是写出来的是攒出来的1.1 单文件失控的典型节奏我先描述一个场景你看看有没有共鸣。一开始你写了个脚本处理数据大概300行跑得很顺畅。后来要加个配置读取变成600行。再后来要接数据库、加日志、处理异常文件突破1500行。你开始频繁用编辑器的“查找”功能找函数定义翻到一半忘了自己在找什么。这不是你编码能力有问题。逻辑复杂到一定程度单文件的信息密度就超出人脑的短期记忆承载能力。真正麻烦的是所有东西都挤在同一个命名空间里。变量a可能在函数A里存了一个字符串在函数B里又被赋值成整数调试的时候你根本分不清这个a到底是哪个阶段的产物。1.2 模块化到底帮你解决了什么模块化的第一层价值不是“代码能跑”而是“代码能看”。拆分之后每个文件有清晰职责数据处理在一个模块数据库访问在另一个模块界面逻辑在第三个模块。调试的时候你很清楚该去哪个文件里找问题而不是在一个2000行的文件里大海捞针。第二层价值是隔离。模块是独立的命名空间模块内部定义的全局变量不会影响其他模块。这意味着你可以放心地在每个模块里使用短变量名不用担心意外覆盖全局状态。第三层价值是可复用。写好的模块就是零件下一个项目直接import过来用不用复制粘贴再改一遍。这三层价值加起来才是“井然有序”的真正含义——它不是强迫症式的整齐而是降低你未来修改代码时踩雷的概率。1.3 什么时候开始拆经验法则当你的单文件代码超过500行或者一个文件里能明显看出超过3个“功能念头”的时候就该考虑拆分了。不用等出了大问题再动手。我自己的判断标准很简单只要发现一个文件里既有“读数据”又有“算指标”又有“画图表”这三种不同性质的事我就条件反射式地开始拆分。三个职责挤在一个文件里未来基本一定会改乱。2. 脚本、模块、包三个听起来像却完全不同的概念2.1 脚本是可运行的模块是可导入的Python里一个.py文件如果它是通过python xxx.py直接运行的我们叫它“脚本”如果它是通过import xxx加载的我们就叫它“模块”。同一个文件其实拥有两种身份关键区别在于谁是入口。脚本有入口、有流程它承载的是“要做一件事”的完整过程模块提供的是能力是“可以被别人借用”的工具集合。所以当你发现自己写的模块一导入就开始跑逻辑说明你把模块当脚本写了。正确的做法是把真正可执行的代码放进if __name__ __main__:代码块里让模块在导入时保持安静。2.2 模块是命名空间的载体import一个模块本质上是在当前执行环境的命名空间里创建一个指向模块对象的引用。这个模块对象拥有自己的全局字典模块里定义的所有函数、类、变量都存放在这个字典中。这也是为什么“模块里的变量”和“当前文件里的变量”完全可以同名而互不干扰。很多新手第一次遇到这个特性会困惑搞懂之后会觉得非常舒服——它让代码在结构上天然拥有“隔离舱室”。2.3 包是装了模块的目录当模块数量多了不可能全堆在同一个目录你需要“包”Package来分层。包是一个带__init__.py文件的目录里面可以放多个模块和多个子包。为什么要__init__.py因为Python需要通过这个文件来识别目录是否是一个包。哪怕文件内容为空也要存在。不过在Python 3.3之后引入了命名空间包namespace package的概念在一定条件下可以没有__init__.py但常规项目里我建议仍然加上这样语义更明确、兼容性更好。举个例子一个典型的项目目录结构myproject/ __init__.py core/ __init__.py models.py services.py api/ __init__.py routes.py utils/ __init__.py helpers.py这个结构一眼就能看出项目有哪些功能域比把所有文件堆在一个目录下清楚得多。3. import背后的机制一次性缓存不是每次重读3.1 import执行的三个步骤很多人对import的理解就是“把代码拿过来用”实际上import背后有三件事在sys.modules里查找模块是否已经导入过如果没有按照sys.path里登记的路径搜索模块文件找到之后执行模块代码创建模块对象存入sys.modules并在当前命名空间里绑定名称。因为这是三个独立过程任何一步出问题都会表现为不同的报错形式。搞清楚这个顺序你在排查导入类错误时会快很多。3.2 sys.path搜索顺序的坑sys.path是一个列表里面存储着所有模块搜索路径。执行import xxx时Python按sys.path的顺序查找先找到谁就用谁。这个顺序往往会制造一些看似莫名其妙的错误。我踩过一个典型坑项目里有个utils.py本地开发一切正常。部署到服务器后因为环境里恰好装了一个第三方库也叫utils而第三方库的路径排在sys.path更前面结果我的代码导入的其实是别人的模块报错信息千奇百怪查了两个小时才定位到。解决方案很直接模块命名不要用太通用的名字utils、common、helper这类尽量避免避免和第三方库冲突项目内部尽量用包内完整路径导入确保路径可控。3.3 sys.modules缓存为什么重复导入不会执行两次很多新手会担心我多个文件都import同一个模块会不会执行很多次答案是不会。第一次导入之后模块对象被放进了sys.modules字典。后续的import语句直接从字典中取值不会重新执行模块代码。只有在解释器重启或者你显式调用importlib.reload()时模块才会重新加载。这个机制对性能是好事但调试时是个坑。你在一个终端里改了模块代码再次import发现还是旧逻辑就是缓存作祟。这时候最好的处理方式是重启Python解释器而不是依赖reload。特别是在做命令行工具开发时每次改动代码后如果发现行为没变第一反应就应该是“刚才那个进程还开着”。3.4 from xxx import yyy和import xxx的区别这是最老的面试题之一但很多人实际上没有真正重视它。import xxx绑定的是模块名你需要用xxx.yyy访问from xxx import yyy则把yyy直接复制到当前命名空间之后直接使用。看起来只是写法差异实际影响很大。from import在多个模块里出现同名函数时最后导入的那个会覆盖前面的而import xxx因为保留了模块名前缀语义和来源都一清二楚。我个人习惯如果在同一个文件里需要用到某个模块的多个属性优先用import 模块名的方式如果明确只需要一个函数而且函数名足够有辨识度才用from import。这让代码的可读性和可追溯性都更好。4. 动手写自己的第一个模块从零到完整导入4.1 模块文件的命名规范与内容组织创建模块的第一步是命名。Python社区推荐小写字母加下划线的形式比如data_cleaner.py、order_service.py。不要用连字符不要用大写字母也不要和标准库、第三方常见模块重名。模块内部的内容组织我建议按这个顺序文件头部写模块级文档字符串一句话说明这个模块是干嘛的导入依赖定义常量定义函数和类如果有可独立运行的测试入口放在if __name__ __main__:里面。有人会问为什么要把文档字符串写在最前面因为当你用help(module)查看帮助时Python返回的就是模块的文档字符串。这是给未来的自己和其他开发者看的说明书一句话也好几句话也好必须有。下面是一个示例模块的完整结构订单折扣计算模块。 提供基于会员等级、订单金额的折扣计算函数。 DEFAULT_DISCOUNT_RATE 0.9 def calculate_discount(amount: float, member_level: int) - float: 根据会员等级计算最终金额。 if member_level 3: return amount * DEFAULT_DISCOUNT_RATE * 0.8 return amount * DEFAULT_DISCOUNT_RATE if __name__ __main__: print(calculate_discount(100, 1))这个模块被导入时不会执行最后的测试打印直接运行python order_service.py时则会看到计算结果。4.2 ifname main让同一个文件两种身份都安全这是模块化开发中使用最频繁的特性之一。当一个文件被直接运行时Python会把__name__设为__main__当它被import时__name__是模块名本身。所以if __name__ __main__:这个判断实际在问——“你是直接运行我还是别人导入我”如果不写这个判断模块里顶层的测试代码在导入时就会立即执行轻则多打印一堆没用的输出重则产生严重副作用。我在项目里见过这样的案例有人把数据库连接初始化的代码写在模块顶层结果任何模块一旦import它数据库连接就被建立一次测试环境和生产环境都深受其害。4.3 相对导入与绝对导入选择权在项目结构模块多了之后包内互相导入很常见。这时候有两种写法。绝对导入from myproject.core import models相对导入from . import models from .models import User绝对导入的优点是路径清晰缺点是包名一旦改动所有导入语句都要跟着改。相对导入在包内部移动模块时更灵活但不支持在直接运行脚本的场景中使用容易触发“attempted relative import with no known parent package”错误。我自己的经验是在正式项目里包内部的模块之间优先用相对导入跨包引用用绝对导入。两者混用时要清楚当前文件是否会被直接运行不要在会被直接运行的文件里写相对导入。4.4all精确控制别人能导入什么当模块变得复杂内部可能在模块作用域里写了很多辅助函数但并不意味着每个函数都要对外暴露。在模块里定义__all__列表可以精确控制from xxx import *时导入哪些名字。__all__ [calculate_discount, DEFAULT_DISCOUNT_RATE]这并不限制import xxx的访问但它是一个明确信号告诉使用这个模块的人哪些内容是有意设计的公共接口哪些只是内部实现细节。对团队协作来说这是非常实用的约定。5. 真实项目里的模块设计策略拆文件也要讲方法论5.1 按职能划分模块的三个层次模块化不只是拆文件而是要按“职责”拆。在实际项目中我习惯把模块分成三类基础设施层访问外部系统、读写文件、连接数据库业务逻辑层处理核心规则、计算、数据转换接口表现层对外提供访问入口比如CLI、HTTP接口。这个分层的核心用意是让“底层能力”和“上层逻辑”解耦。数据库迁移时你只需要改基础设施层的模块业务规则变化时只需要改业务逻辑层界面调整时只需要改接口表现层。如果三层之间互相越界项目就很容易变成一团乱麻。5.2 公共工具模块要克制utils不是垃圾场几乎每个项目都会有一个utils或common模块。我见过不少项目这个模块最后变成了垃圾场——什么函数都往里塞几千行不分类别。utils 应该只放那些“没有业务含义”的通用函数比如时间格式转换、字符串清理、校验手机号。一旦一个函数涉及了业务名词比如“计算订单折扣”它就不该放在 utils 里应该放回对应的业务模块。这个原则坚持下来你的代码会好维护很多。我实测过一个项目里 utils 模块超过1000行基本就是设计失控的信号意味着你需要重新审视职责边界。5.3 依赖方向模块之间尽量是单向的这是我在大型项目里吃过亏后才深刻理解的一点模块之间应该有明确的依赖方向而且最好是单向的。举个例子A模块调用B模块B模块就不要反向调用A。如果出现互相引用代码会很快陷入“改一处动全身”的困境。如果你的代码里出现循环导入那基本就是依赖方向出了问题。怎么检查依赖方向最简单的方法画一幅模块关系草图看看有没有环状结构。有环就说明某个模块的职责边界划得不清楚需要重新思考拆分方式。工具层面可以使用pydeps这类依赖分析工具把模块关系可视化效果比肉眼扫描代码好很多。5.4 接口先行实现后补我要额外提一个设计习惯模块划分时先定义接口再写实现。在一个模块里先把对外暴露的函数签名和类结构写清楚再慢慢填充内部逻辑。这样做的价值在于你逼自己先思考“这个模块的使用方需要什么”而不是“我要怎么写这个模块”。接口优先的模块设计通常更稳定、更清晰也让团队协作变得顺畅得多。6. 四个高频模块化坑我替你们踩过了6.1 循环导入——最经典的模块化事故先看一个典型报错ImportError: cannot import name xxx from partially initialized module a (most likely due to a circular import)报错信息已经非常明确循环导入。A模块import BB模块又import A两边都还没初始化完Python就转晕了。循环导入的本质是模块初始化顺序问题。Python执行import时会先创建模块对象再执行模块内容。如果A先开始执行执行到一半又去import BB反过来import A而A此时还没有执行完很多属性还不存在就会报错。解法的优先级我是这样排列的把公共逻辑抽到第三个模块打破循环把一方对另一方的导入放到函数内部延迟导入梳理不清就重构模块边界。我在实际项目中优先推荐第一种方式。延迟导入可以应急但长期来看循环本身就是设计缺陷的报警器不能靠小动作掩盖。6.2 模块路径找不到为什么我的文件明明存在却导入失败这个坑经常在项目目录结构变化之后出现。你把某个模块从src移到了utils结果所有import全部断裂报ModuleNotFoundError。原因通常是代码使用了隐式的模块名导入文件移动后原来能解析到的道路变了。而Python不会自动搜索“当前文件的父目录”当项目根它只认sys.path里列出的路径。解决了办法有几个按推荐程度排序把项目做成可安装的包运行pip install -e .导入路径就稳定了在入口文件里手动把项目根目录插入sys.path写一个统一的启动脚本设置好PYTHONPATH环境变量。第1种方式最规范我强烈建议项目一上来就按这个方式组织。第2种方式可作为团队内部快速解决的手段但不要滥用因为不同机器上的路径布局往往不一致。import sys from pathlib import Path PROJECT_ROOT Path(__file__).parent.parent sys.path.insert(0, str(PROJECT_ROOT))这段代码放在项目入口文件最顶部能快速解决本地运行时的路径问题。但记住它解决的是“开发环境顺手”的问题不是“发布环境稳健”的问题。6.3 命名空间污染你的全局变量正在互相干扰模块为每个文件隔离了命名空间但如果你在一个模块内部大量使用全局变量还是容易翻车。最典型的问题模块顶层的可变对象比如一个列表被多个函数共用。一个函数往里面追加了一条数据其他函数全部受影响查Bug的时候异常头疼。模块级的全局变量不是不能用但要有明确约定要么全部通过函数参数传递要么把共享状态封装成类实例把状态放在实例内部而不是模块顶层。后者的可读性和可测试性都更好也更符合“模块作为工具库、实例承载状态”的设计思路。6.4 __pycache__与陈旧缓存改了代码却不生效Python运行时会生成__pycache__目录里面是编译后的.pyc文件。绝大多数情况下这是正常的性能优化无感且无害。但有一种情况非常坑你改了源码运行时却依然使用旧逻辑特别是在IDE里反复运行同一个文件时。虽然Python通常会根据源码文件的时间戳自动判断是否重新编译但如果时间戳相同或者IDE缓存出现问题就会出现这种诡异现象。常规处置删掉__pycache__目录重启解释器再运行。如果你在用Jupyter notebook记得重启kernel否则模块缓存会让你的调试完全失效。这类问题跟项目模块化的关系不大但在大型项目里出现的频率非常高提前知道可以省下不少排查时间。最后分享一条很主观的体会模块化的最终目的不是追求结构图好看而是减少“我不知道这个改动会影响谁”的焦虑。每当你准备动一个函数之前心里能有明确的范围感——知道它属于哪个模块、被谁依赖、改了会影响哪些地方——这种确定性比任何重构技巧都让人安心而这种确定性正是模块给的。模块划分其实没有标准答案不同团队、不同阶段的取舍各不相同。重要的是你始终保有一种意识代码是写给未来的人看的那个人很可能是三个月后的自己。把事情说清楚比把事情“写得聪明”重要得多。所以别急着追求一上来就完美架构先把今天这个快要失控的“大文件”拆开就已经是很大的一步了。

相关新闻

eFuse与MCU协同的工业电源路径保护设计:从阈值计算到状态机实战

eFuse与MCU协同的工业电源路径保护设计:从阈值计算到状态机实战

1. 先从一次“上电即烧板”的教训说起:为什么电源路径需要保护如果你做过工业网关或者嵌入式控制板,一定见过这种场景:样机在实验室里跑得好好的,一到现场接上带了电机、加热器、多个传感器的负载,12V电源线稍微抖一下…

2026/10/7 11:38:51 阅读更多 →
pstack原则06最小化读者负担:降低代码审查认知负荷的8条实用清单

pstack原则06最小化读者负担:降低代码审查认知负荷的8条实用清单

pstack原则06最小化读者负担:降低代码审查认知负荷的8条实用清单 【免费下载链接】pstack-claude Claude Code, Codex, Copilot, Pi, OpenCode, Gemini, and Prime Agent versions of Potetos pstack. Rigorous agent workflows with Cursor primitives translated …

2026/10/7 11:37:51 阅读更多 →
AEM水电解实时监测系统设计:以电源为中枢的高精度动态表征

AEM水电解实时监测系统设计:以电源为中枢的高精度动态表征

1. 项目概述:为什么一个AEM水电解研究电池的监测,值得花整整三天搭一套专用系统?“Monitoring an AEM Water Electrolysis Research Cell”——这个标题看起来平平无奇,就是实验室里再普通不过的一句设备操作记录。但如果你真在电…

2026/10/7 11:37:51 阅读更多 →

最新新闻

M.2 E Key WiFi蓝牙模块硬件设计与调试实战指南

M.2 E Key WiFi蓝牙模块硬件设计与调试实战指南

1. 从一块小板子说起:M.2 E Key接口的WiFi与蓝牙模块到底怎么设计 搞硬件设计的朋友大概率都碰过这样的场景:项目立项,主控选好了,功能列表里赫然写着“支持WiFi 6 BT 5.2”,采购那边催着要模块选型,Layou…

2026/10/7 12:47:46 阅读更多 →
如何用e2e快速测试React受控组件:表单、无限滚动与虚拟列表实战指南

如何用e2e快速测试React受控组件:表单、无限滚动与虚拟列表实战指南

如何用e2e快速测试React受控组件:表单、无限滚动与虚拟列表实战指南 【免费下载链接】e2e Next generation e2e testing framework for web and mobile apps. 项目地址: https://gitcode.com/GitHub_Trending/e2e6/e2e e2e 是一个开源的下一代端到端&#xf…

2026/10/7 12:47:46 阅读更多 →
Spring Boot+Vue记账系统开发实战:从数据库设计到JWT认证部署

Spring Boot+Vue记账系统开发实战:从数据库设计到JWT认证部署

简介:基于Springboot与Vue构建的大学生智能消费记账系统,是一份适合毕业设计、课程设计或前后端分离实战入门的完整源码案例。系统围绕大学生记账场景,提供账单管理、分类统计、数据可视化、预算提醒等功能,帮助你理解SpringBoot自…

2026/10/7 12:47:46 阅读更多 →
基于SSM的家政保洁预约系统实战:从需求拆解到事务与拦截器排坑

基于SSM的家政保洁预约系统实战:从需求拆解到事务与拦截器排坑

简介:这是一套基于SSM框架、结合Vue与ElementUI前端、MySQL数据库的家政保洁预约管理系统完整源码包,面向Java开发学习者、毕业设计学生以及需要快速构建家政服务预约平台的技术人员。项目覆盖用户信息管理、预约服务等核心模块,后端采用SSM&…

2026/10/7 12:47:46 阅读更多 →
He I 59.14121nm谱线:从物理原理到太阳EUV观测应用解析

He I 59.14121nm谱线:从物理原理到太阳EUV观测应用解析

1. 先搞明白:这条He I 59.14121nm线到底是什么拿到“学习He I 光谱59.14121nm线”这个题目时,我第一反应是:这可不是一条能随手拿氦灯在实验室里“点个火”就能轻松搞定的谱线。它位于极紫外波段(EUV),波长…

2026/10/7 12:47:46 阅读更多 →
基于Java JSP的汽车维修保养管理系统:架构、事务与部署实战

基于Java JSP的汽车维修保养管理系统:架构、事务与部署实战

简介:这是一份基于 JavaJSP 的汽车维修保养管理系统完整源码与数据库文件,面向毕业设计场景,适合计算机专业学生完成课程设计或毕设项目。系统采用 JSP、jQuery 前端,Servlet、JDBC 后端,按管理员与用户双角色划分&…

2026/10/7 12:46:45 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 11:43:46 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →