Python中的包和模块实例
前言讲模块和包的文章很多但大多停在「import可以导入东西」这一层。真到动手写项目时问题会变成日志工具放哪、配置怎么传、公共函数和被业务模块共享的数据怎么组织、脚本入口怎么留。这些都属于目录结构与导入路径的工程问题光知道语法不够。本文用一个可以照着跑的小项目把「模块、包、子包、__init__.py、__main__入口、sys.path」串起来。所有示例只依赖标准库复制到磁盘上就能执行。需要先纠一个说法题面里说的「包和模块实例」指的是包与模块的实例代码即一组用来演示包/模块用法的可运行示例它不是指「对包和模块做实例化」这种操作包和模块本身不是用来new的对象。本文按前者写。一、项目骨架假设我们要写一个小型的「库存记账」工具需求很朴素记录商品、算总价、按类别汇总、能导出文本报表。结构大致这样inventory/├── app.py└── inv/├── __init__.py├── models.py├── calc.py├── report.py└── formats/├── __init__.py├── plain.py└── table.py这里inv是包inv.formats是子包每个.py是模块。分层的理由models只放数据结构calc只放计算report负责编排formats承担输出格式将来加个 JSON 输出只需要新增一个模块。二、数据模型与计算模块数据模型模块# 适用于 Python 3.8# 文件名 inv/models.py库存的数据结构。from dataclasses import dataclass, fielddataclassclass Item:name: strcategory: strunit_price: floatquantity: int 1def total(self) - float:单价乘以数量。return self.unit_price * self.quantitydataclassclass Inventory:items: list field(default_factorylist)def add(self, item: Item) - None:加入一件商品名字重复时累加数量。for existing in self.items:if existing.name item.name and existing.category item.category:existing.quantity item.quantityreturnself.items.append(item)dataclasses从 Python 3.7 起提供3.8 及以上都能用。field(default_factorylist)是必须的写法如果直接写items: list []所有Inventory实例会共享同一个列表这是可变默认值的经典坑。计算模块与子包# 适用于 Python 3.8# 文件名 inv/calc.py基于 Inventory 的统计计算。from collections import defaultdictfrom .models import Inventorydef grand_total(inv: Inventory) - float:所有条目的总金额。return sum(item.total() for item in inv.items)def by_category(inv: Inventory) - dict:按类别汇总金额返回普通字典。buckets defaultdict(float)for item in inv.items:buckets[item.category] item.total()return dict(buckets)def top_items(inv: Inventory, limit: int 3) - list:按金额从高到低取前若干条limit 小于等于 0 时返回空列表。if limit 0:return []ordered sorted(inv.items, keylambda i: i.total(), reverseTrue)return ordered[:limit]注意from .models import Inventory这个相对导入它要求calc模块是以包内身份被加载的也就是从包外面用python -m或import进入而不能直接python inv/calc.py跑。defaultdict(float)的默认值是0.0所以第一次不会报KeyError。最后dict(buckets)把defaultdict转成普通字典避免把这个「会自动补键」的特性泄漏给调用者。子包里的输出格式模块# 适用于 Python 3.8# 文件名 inv/formats/plain.py纯文本输出。def render(rows):rows 是 (名称, 类别, 金额) 的序列返回多行文本。lines []for name, category, amount in rows:lines.append(f{name}\t{category}\t{amount:.2f})return \n.join(lines)# 适用于 Python 3.8# 文件名 inv/formats/table.py对齐的表格输出。def render(rows):rows 是 (名称, 类别, 金额) 的序列返回对齐后的多行文本。materialized list(rows)if not materialized:return (空)widths [max(len(str(row[i])) for row in materialized)for i in range(3)]sep .join(- * (w 2) for w in widths) lines [sep]for row in materialized:cells (str(row[0]).ljust(widths[0]),str(row[1]).ljust(widths[1]),f{row[2]:.2f}.rjust(widths[2]),)lines.append(| | .join(cells) |)lines.append(sep)return \n.join(lines)子包的__init__.py把两个渲染函数都收进来方便按名字取# 适用于 Python 3.8# 文件名 inv/formats/__init__.py输出格式集合。from .plain import render as render_plainfrom .table import render as render_tableRENDERERS {plain: render_plain,table: render_table,}__all__ [render_plain, render_table, RENDERERS]顶层包的__init__.py# 适用于 Python 3.8# 文件名 inv/__init__.py库存记账小工具。from .calc import grand_total, by_category, top_itemsfrom .models import Item, Inventory__all__ [Item,Inventory,grand_total,by_category,top_items,]__version__ 1.2.0report.py负责把上面几块缝起来# 适用于 Python 3.8# 文件名 inv/report.py报表编排。from .calc import by_category, grand_totalfrom .formats import RENDERERSdef category_rows(inv, fmttable):返回按类别汇总的渲染结果。if fmt not in RENDERERS:raise ValueError(f未知格式{fmt!r}可选 {sorted(RENDERERS)})rows [(name, 类别合计, amount) for name, amount in sorted(by_category(inv).items())]return RENDERERS[fmt](rows)def summary(inv):返回 (条目数, 总金额) 二元组。return len(inv.items), grand_total(inv)三、入口脚本与__main__# 适用于 Python 3.8# 文件名 inventory/app.py命令行入口。from inv import Inventory, Itemfrom inv.report import category_rows, summarydef build_demo():构造一份演示数据。inv Inventory()inv.add(Item(杯子, 厨房, 12.5, 4))inv.add(Item(盘子, 厨房, 8.0, 6))inv.add(Item(台灯, 照明, 45.0))inv.add(Item(灯泡, 照明, 6.5, 10))inv.add(Item(灯泡, 照明, 6.5, 2)) # 名字与类别都相同数量累加return invdef main():inv build_demo()print(category_rows(inv, fmttable))count, total summary(inv)print(f条目数{count}总金额{total:.2f})if __name__ __main__:main()推导一遍中间值杯子 12.5 × 4 50.0盘子 8.0 × 6 48.0台灯 45.0 × 1 45.0灯泡 6.5 × 10 65.0再加 6.5 × 2 13.0累加后数量为 12金额 78.0。厨房合计 98.0照明合计 123.0合计 221.0。条目数为 4灯泡两行被合并成一条。运行方式与对应结果运行命令是否成功原因在inventory目录下python app.py成功脚本目录进sys.pathinv可被搜到在inventory上一级python inventory/app.py成功同上加入的是inventory目录在inv目录下python calc.py失败相对导入from .models import ...找不到父包在inventory下python -m inv.calc可导入但无输出以包内身份加载相对导入成立四、再谈模块搜索开发期怎么让包能被找到如果入口脚本不在包的旁边最省事的两种办法是第一种设置环境变量PYTHONPATH把包的父目录加进去export PYTHONPATH/path/to/projectpython /path/to/project/tools/run.py第二种在代码里临时改sys.path仅限临时调试# 适用于 Python 3.8import sysfrom pathlib import Pathsys.path.insert(0, str(Path(__file__).resolve().parent))import inv # noqa: E402 必须在改完 sys.path 之后再导入注意import inv必须写在改sys.path之后。把导入写在文件顶部是更好的习惯但那种情况下改路径的语句就来不及生效了——这正是这类「延迟导入」写法存在的理由它不是为了炫技。生产环境正确做法是把包做成可安装的分发件配pyproject.toml用pip装进环境而不是靠脚本里改路径。常见坑点1. 用可变对象当 dataclass 字段默认值❌items: list []多个实例共享同一个列表一改全改。 ✅items: list field(default_factorylist)。2. 直接运行包内模块❌python inv/calc.py一遇到相对导入就报错。 ✅ 从包的父目录用python -m inv.calc或者干脆只运行包外的入口脚本。3. 把渲染函数当成字符串格式名传❌ 传fmtcsv却忘了在RENDERERS里注册KeyError 消息很难读。 ✅ 先if fmt not in RENDERERS判断再取抛出带上可选值的ValueError示例里就是这么做的。4. 子包__init__.py里提前导入重型模块❌ 在formats/__init__.py顶部无条件导入一个会读系统字体、加载大词库的模块结果只用纯文本输出的人也被拖慢。 ✅ 把重的东西放进函数内部按需导入或者分拆成多个子包。5. 累加逻辑写错导致数据被覆盖❌ 在add()里发现重名就直接return数量没累加。 ✅ 找到同名的就existing.quantity item.quantity再返回。6. 忘记if __name__ __main__:❌ 把main()的调用直接写在文件顶层别人import app时也跟着执行一遍演示数据打印。 ✅ 用if __name__ __main__:保护只在直接运行时执行。7. 模块名与标准库冲突❌ 把包里的工具模块命名为types.py、copy.py、queue.py导入标准库同名模块时被自己的文件截胡。 ✅ 改名加前缀如inv_types.py、inv_queue.py。8. 用 Python 2 的写法组织包❌ 沿用隐式相对导入、print语句、dict.iteritems()。Python 2.7 已于2020 年 1 月 1 日停止维护。 ✅ 一律用显式相对导入或绝对导入Python 3 语法。总结层次载体职责示例包inv/目录顶层命名空间与公开接口子包inv/formats/目录按功能再分一层模块models.py/calc.py单一职责的代码单元入口app.py编排与命令行启动包和模块的组织方式本质上是在回答「谁依赖谁」这个问题。把数据结构、计算逻辑、输出格式、入口脚本分成四层可以让依赖方向始终单向入口依赖一切格式和计算互不依赖模型谁都不依赖。这样以后加功能只是往对应模块里塞函数而不是回头改一堆 import。写完之后用python -m 包.模块而不是直接运行包内文件是保住这套结构不被破坏的最简单的一条纪律。

相关新闻

Oracle数据库RMAN备份与恢复:从归档模式到异机恢复的完整实践指南

Oracle数据库RMAN备份与恢复:从归档模式到异机恢复的完整实践指南

简介:《Oracle数据库RMAN备份与恢复》PDF文档面向Oracle数据库管理员及运维人员,系统讲解RMAN备份恢复技术,帮助读者理解备份策略定制与恢复操作,解决数据安全与故障恢复的核心问题。资源为单一PDF电子文档,压缩包容量…

2026/10/9 9:48:05 阅读更多 →
Python中的并发编程asyncio库入门使用

Python中的并发编程asyncio库入门使用

前言 asyncio 是 Python 标准库里的异步框架,但它不是「一个函数」,而是一整套协作式并发的基础设施。新手常犯的错,是把 asyncio 的 API 当成 threading 的等价物来用——随手 await 两下,却发现根本没有并发。 这篇是API 速查 …

2026/10/9 9:48:05 阅读更多 →
SMCC特征提取+BP神经网络:玉米种子活力分级实战

SMCC特征提取+BP神经网络:玉米种子活力分级实战

简介:这份PDF文献面向从事玉米种子检测、育种与农业工程的研究人员及机器学习初学者,聚焦种子活力多等级快速无损分级这一实际问题。文中以人工加速老化制备五个活力等级样本,用近红外漫反射光谱仪采集光谱,对比主成分分析与SMCC特…

2026/10/9 9:47:05 阅读更多 →

最新新闻

等保2.0数据库测评通关指南:MySQL/Oracle/SQL Server/PostgreSQL/Redis五类数据库加固与自查

等保2.0数据库测评通关指南:MySQL/Oracle/SQL Server/PostgreSQL/Redis五类数据库加固与自查

简介:这份作业指导书面向数据库安全测评人员、等保合规工程师及运维人员,系统梳理了MySQL、Oracle、SQL Server、Postgres、Redis五类主流数据库在等保测评中的实操要点,帮助读者快速定位各数据库的测评项与查询方法。资源包内含1个docx文档&…

2026/10/9 11:09:59 阅读更多 →
基于LoRA微调的中文医疗问答机器人实战:从数据构造到量化部署

基于LoRA微调的中文医疗问答机器人实战:从数据构造到量化部署

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

2026/10/9 11:09:59 阅读更多 →
EtherCAT与FSoE协议栈深度解析:从报文结构到安全配置实战

EtherCAT与FSoE协议栈深度解析:从报文结构到安全配置实战

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

2026/10/9 11:09:59 阅读更多 →
pstack-claude:命令行级本地化Claude集成方案

pstack-claude:命令行级本地化Claude集成方案

1. 项目概述:pstack-claude 是什么,它解决的是哪类真实开发痛点?pstack-claude 这个名字乍看像一个工具组合词,但拆开来看,“pstack”是 Linux 系统中一个真实存在的诊断命令,用于打印指定进程的调用栈&…

2026/10/9 11:09:59 阅读更多 →
pstack诊断Claude工具卡死:从调用栈定位Node.js阻塞问题

pstack诊断Claude工具卡死:从调用栈定位Node.js阻塞问题

1. “pstack-claude”不是工具名&#xff0c;而是调试现场的命名习惯你搜“pstack-claude”&#xff0c;大概率是在终端里敲下pstack <pid>后&#xff0c;突然发现进程名里带claude字样——比如claude-code-server、claude-desktop或某个本地部署的codex服务进程。这时候…

2026/10/9 11:09:59 阅读更多 →
力扣模拟题刷题指南:从拆解思路到经典题单与面试策略

力扣模拟题刷题指南:从拆解思路到经典题单与面试策略

做力扣模拟题&#xff0c;最容易被低估&#xff0c;也最容易翻车。我刷了三百多道题之后回头看&#xff0c;真正在面试现场把我救下来的&#xff0c;往往不是那些需要灵光一现的DP难题&#xff0c;而是老老实实按题目要求一步步模拟的“体力活”。今天这篇就把模拟题这件事聊透…

2026/10/9 11:08:57 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题&#xff0c;隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题&#xff0c;排查到最后发现是ZonedDateTime序列化后时区丢了&#xff0c;用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问&#xff1a;办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好&#xff0c;问题是工作场景经常要在几处环境之间来回切换&#xff0c;每次都先登录跳板机再层层代理&#xff0c;实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及&#xff0c;但真正动手搭过一套能跑起来的 Agent 系统的人都知道&#xff0c;从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地&#xff0c;从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

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/8 15:26:32 阅读更多 →
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/8 15:26:40 阅读更多 →
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/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →