查看全课大纲(第 35 / 51 节)
- 1.Python 是什么?为什么值得学?
- 2.如何为 Python 编程准备一个“全家桶”环境?
- 3.Python 有哪几种执行方法?
- 4.行与缩进到底有什么用?
- 5.注释有什么用,怎么写?
- 6.变量:如何给数据起个好名字?
- 7.运算符的优先级和常见坑点是什么?
- 8.Python 中,数字和布尔值究竟有哪几种?
- 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
- 10.如何将程序的结果展示给用户或保存下来?
- 11.CSV 文件怎么读怎么写才方便?
- 12.列表是什么,如何创建和访问它?
- 13.列表的修改操作,有哪些方法?
- 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
- 15.判断语句:如何让程序根据条件做不同的事?
- 16.for 循环如何遍历序列中的元素?
- 17.while 循环什么时候才会停?
- 18.while 循环的结束条件,你写对了吗?
- 19.循环中途想提前结束,怎么办?
- 20.`break` 和 `continue` 到底有什么区别?
- 21.列表推导式如何让代码更简洁高效?
- 22.字符串怎么定义、拼接和操作?
- 23.元组:为什么定义单个元素时要加逗号?
- 24.集合(set)怎么用?如何快速去重和做集合运算?
- 25.字典是什么,为什么比列表更方便?
- 26.字典的增删改查与遍历,如何避免 KeyError?
- 27.函数里的变量,为什么有的能用有的不能用?
- 28.在函数内部修改全局变量,为什么有时不生效?
- 29.函数里能修改全局变量吗?返回值怎么用?
- 30.匿名函数:如何用 lambda 快速定义简单函数?
- 31.面向对象编程是什么,为什么需要它?
- 32.类中的方法和属性到底怎么定义和使用?
- 33.如何控制对对象内部属性和方法的访问?
- 34.异常发生时,程序会怎样继续执行?
- 35.如何组织代码:包和模块
- 36.`if __name__ == '__main__'` 到底有什么用?
- 37.如何用 IDE 的 Debug 功能排查程序错误?
- 38.Matplotlib 绘图的第一步该做什么?
- 39.Figure 是什么,如何设置图形尺寸?
- 40.如何自定义 Matplotlib 的坐标轴?
- 41.如何为图表添加图例,让不同曲线一目了然?
- 42.饼图怎么画才能直观展示数据占比?
- 43.如何用 Matplotlib 绘制散点图?
- 44.柱状图怎么画?
- 45.如何使用 Matplotlib 绘制等高线图?
- 46.如何在 Matplotlib 中绘制子图?
- 47.zip 函数如何把多个列表“打包”在一起?
- 48.如何给可迭代对象加上索引?
- 49.random 模块如何生成随机数?
- 50.math 模块怎么用?
- 51.Counter 如何快速统计元素出现次数?
如何组织代码:包和模块
约 21 分钟
小象实战讲义 · Python 编程从入门到实践
当我们编写的代码越来越多,功能越来越复杂时,把所有代码都放在一个文件里会变得难以管理。Python 用两个层次来组织代码:包(Package)与模块(Module),让代码结构清晰,也方便复用。本节将学习如何创建、导入和使用包与模块,这是编写大型项目的基础。
💡 核心导读
- 模块就是一个
.py文件,里面可以定义函数、类等。 - 包是一个目录,里面可以放多个模块;按传统包规范,目录里还会放一个
__init__.py文件。 - 使用
import语句可以导入模块或包,从而使用其中定义的功能。 __init__.py文件在包被导入时自动执行,常用于包的初始化。- 了解 Python 的模块搜索路径,知道它如何找到你要导入的模块。
模块和包是什么
在 Python 中,一个 .py 文件就是一个模块(Module)。模块里可以定义函数、类、变量等。例如,一个名为 calculator.py 的文件就是一个模块,里面可以定义 add、subtract 等函数。
所谓包(Package),就是一个存放多个模块的目录,目录里通常还会有一个特殊文件 __init__.py。包是用来组织相关模块的,就像一个文件夹用来整理文件。
它们的关系可以这样理解:
- 包 → 目录(文件夹)
- 模块 → 目录里的
.py文件 - 函数/类 → 文件里的具体代码
例如,一个项目可能这样组织:
my_project/
├── main.py
├── utils/ # 一个包
│ ├── __init__.py # 包的初始化文件
│ ├── file_utils.py # 模块
│ └── math_utils.py # 模块
└── models/ # 另一个包
├── __init__.py
└── user.py导入模块的几种方式
1. 导入整个模块
使用 import 语句后跟模块名(不含 .py 后缀)。例如 business 包下有 module1.py,就写 import business.module1。
2. 一次导入多个模块
可以在一个 import 语句中导入多个模块,用逗号分隔。
下面用内存中的模块对象模拟包 business、tools 及其 module1 模块(真实项目中,它们就是两个目录下的 module1.py 文件),演示上面两种导入方式:
import sys, types
# 在内存中模拟 business/ 和 tools/ 两个包及其 module1 模块
def project_info():
print("*" * 20)
print(" " * 4, "Python项目", " " * 4)
print("*" * 20)
def tool_info():
print("*" * 20)
print(" " * 4, "常用工具", " " * 4)
print("*" * 20)
business = types.ModuleType("business")
business.__path__ = []
business_module1 = types.ModuleType("business.module1")
business_module1.project_info = project_info
business.module1 = business_module1
tools = types.ModuleType("tools")
tools.__path__ = []
tools_module1 = types.ModuleType("tools.module1")
tools_module1.tool_info = tool_info
tools.module1 = tools_module1
sys.modules["business"] = business
sys.modules["business.module1"] = business_module1
sys.modules["tools"] = tools
sys.modules["tools.module1"] = tools_module1
# 方式一:导入整个模块
import business.module1
business.module1.project_info()
# 方式二:一次导入多个模块,用逗号分隔
import business.module1, tools.module1
business.module1.project_info()
tools.module1.tool_info()********************
Python项目
********************
********************
Python项目
********************
********************
常用工具
********************3. 给模块起别名
如果模块名很长,可以用 as 给它起一个短别名,之后调用时就不必写全名。
4. 从模块中导入特定函数
如果只需要模块中的某个函数,可以用 from ... import ... 语法,调用时直接写函数名,不需要模块名前缀。
import sys, types
def project_info():
print("*" * 20)
print(" " * 4, "Python项目", " " * 4)
print("*" * 20)
def tool_info():
print("*" * 20)
print(" " * 4, "常用工具", " " * 4)
print("*" * 20)
business = types.ModuleType("business")
business.__path__ = []
business_module1 = types.ModuleType("business.module1")
business_module1.project_info = project_info
business.module1 = business_module1
tools = types.ModuleType("tools")
tools.__path__ = []
tools_module1 = types.ModuleType("tools.module1")
tools_module1.tool_info = tool_info
tools.module1 = tools_module1
sys.modules["business"] = business
sys.modules["business.module1"] = business_module1
sys.modules["tools"] = tools
sys.modules["tools.module1"] = tools_module1
# as 起别名:之后用 bm 代替 business.module1
import business.module1 as bm
bm.project_info()
# from 包.模块 import 函数:调用时不再需要模块名前缀
from tools.module1 import tool_info
tool_info()********************
Python项目
********************
********************
常用工具
********************包的特殊文件:__init__.py
说明:课程录制时(Python 3.6)按传统包规范讲解,每个包目录都会放一个
__init__.py。Python 3.3 及以后支持命名空间包,没有__init__.py的目录也能被导入;但常规项目仍建议每个包目录放一个__init__.py,便于初始化与工具识别。
__init__.py 文件有两个主要作用:
- 声明这是一个常规包。传统规范下,没有这个文件的目录不会被当成包;现代 Python 的命名空间包允许省略它,但常规项目仍建议保留,便于初始化与工具识别。
- 在包被导入时自动执行,可用于初始化包。
例如,在 business/__init__.py 中写一句打印语句:
# business/__init__.py
print("__init__.py 被自动调用")之后执行 import business.module1 时,Python 会先执行 business/__init__.py,再加载 module1,所以会先看到初始化语句的输出:
__init__.py 被自动调用
********************
Python项目
********************使用 __all__ 控制 from package import *
在 __init__.py 中定义 __all__ 列表,可以控制当使用 from package import * 时,哪些模块会被导入。下面的例子用内存模块模拟 demo_pkg 包,并把 __all__ 设为只包含 mod_a:
import sys, types
def func_a():
print("这是模块A的功能")
def func_b():
print("这是模块B的功能")
demo_pkg = types.ModuleType("demo_pkg")
demo_pkg.__path__ = []
mod_a = types.ModuleType("demo_pkg.mod_a")
mod_a.func_a = func_a
mod_b = types.ModuleType("demo_pkg.mod_b")
mod_b.func_b = func_b
demo_pkg.mod_a = mod_a
demo_pkg.mod_b = mod_b
# 相当于在 demo_pkg/__init__.py 中写 __all__ = ["mod_a"]
demo_pkg.__all__ = ["mod_a"]
sys.modules["demo_pkg"] = demo_pkg
sys.modules["demo_pkg.mod_a"] = mod_a
sys.modules["demo_pkg.mod_b"] = mod_b
from demo_pkg import * # 只导入 __all__ 中列出的 mod_a
mod_a.func_a()
# mod_b.func_b() # 取消注释会报错:NameError: name 'mod_b' is not defined这是模块A的功能Python 如何找到模块:搜索路径
当你执行 import something 时,Python 会在一系列目录中查找 something 模块。这个查找顺序存储在 sys.path 列表中。
搜索顺序:Python 按 sys.path 列表中的路径顺序逐项查找。列表的第一项通常是当前脚本所在目录;在交互式环境(如 Jupyter)下则为空字符串,表示当前工作目录。因此放在当前目录下的包和模块可以被直接导入。
常见场景:如果你的模块不在 sys.path 包含的路径中,导入会失败。这时可以临时修改 sys.path。下面的演示不打印具体路径(不同电脑的路径各不相同),只验证追加是否生效:
import sys
before = len(sys.path)
sys.path.append("my_modules")
print("追加后条目数增加:", len(sys.path) - before)
print("my_modules 已在搜索路径中:", "my_modules" in sys.path)追加后条目数增加: 1
my_modules 已在搜索路径中: True📝 动手练一练
- 模块导入练习:创建一个名为
greetings.py的模块,在里面定义一个函数say_hello(name),功能是打印"Hello, [name]!"。然后在另一个脚本中导入这个模块并调用该函数。
👉 点击查看参考答案
思路:先创建模块文件,定义函数;再在主脚本中导入并调用。真实项目中 greetings.py 是磁盘上的文件,这里把它放进内存模块来演示导入过程。
import sys, types
# 相当于 greetings.py 文件中的内容
greetings = types.ModuleType("greetings")
def say_hello(name):
print("Hello, {}!".format(name))
greetings.say_hello = say_hello
# 让 Python 能找到名为 greetings 的模块
sys.modules["greetings"] = greetings
# 导入并使用
import greetings
greetings.say_hello("小象")Hello, 小象!- 包结构理解:以下目录结构是一个有效的 Python 包吗?为什么?
myapp/ ├── utils/ │ ├── helper.py │ └── validator.py └── main.py
👉 点击查看参考答案
在现代 Python(3.3+)中,这个目录可以被导入:utils/ 没有 __init__.py 时会被当作命名空间包,from utils import helper 依然能成功。不过按常规项目规范,仍建议在 utils/ 下放一个空的 __init__.py,便于初始化与工具识别。
常见错误
| 错误写法 | 现象或报错 | 原因 | 正确写法 |
|---|---|---|---|
包目录下漏写 __init__.py,却依赖包的初始化逻辑 | 现代 Python(3.3+)仍能正常 import,但 __init__.py 里的初始化代码不会执行,包可能没有按预期准备好 | Python 3.3+ 支持命名空间包,没有 __init__.py 也能导入;但常规项目仍建议放 __init__.py,便于初始化与工具识别 | 在包目录下创建 __init__.py,把初始化代码写进去 |
from mymodule import * 但期望导入所有函数 | 可能只导入了一部分,或者报未定义错误。 | 如果没有在模块中定义 __all__ 列表,import * 只会导入不以单下划线(_)或双下划线(__)开头的名字,行为不易预期。 | 明确导入需要的函数:from mymodule import func1, func2。或在模块中定义 __all__ = ["func1", "func2"]。 |
模块文件名与标准库模块重名(如 base64.py) | 导入的不是标准库的 base64 模块,而是同名的本地文件。 | 纯 Python 实现的标准库模块会被当前目录下的同名文件覆盖(内置模块如 math 不受影响)。 | 避免使用与标准库模块相同的文件名。 |
| 修改了模块代码后,重新导入发现没变化 | 代码行为还是旧的。 | 已导入的模块会被运行环境缓存,不会自动重新读取源文件。 | 重启 Python 解释器;在 Jupyter 中执行 Kernel Restart,再重新导入。 |
小结
- 模块是
.py文件,包是用于组织模块的目录(传统项目会在其中放一个__init__.py)。 - 使用
import module导入整个模块,用module.func()调用函数。 - 使用
from module import func直接导入特定函数,调用时无需模块名前缀。 - 可以用
as给模块或函数起别名,简化代码。 __init__.py在包被导入时自动执行,常用于初始化。其中的__all__列表控制from package import *的行为。- Python 按
sys.path中的路径顺序搜索模块。自定义模块需要确保其所在路径在sys.path中。
📋 行动清单
- 尝试创建一个自己的包,包含至少两个模块,并在模块中定义不同的函数。
- 使用
import和from ... import ...两种方式导入你的模块并调用函数,体会区别。 - 在包的
__init__.py中添加一句打印语句,然后导入包下的模块,观察打印语句是否执行。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第4章代码包:包和模块(含1 个 Notebook、5 个 .py)ZIP · 2KB