文章目录
- [Python 模块与包](#Python 模块与包)
-
- [1. 什么是功能](#1. 什么是功能)
- [2. 什么是模块](#2. 什么是模块)
- [3. 什么是包](#3. 什么是包)
- [4. 为什么要引入这些概念?](#4. 为什么要引入这些概念?)
- [5. 如何导入:先理清"路径"与"目标"](#5. 如何导入:先理清“路径”与“目标”)
-
- [5.1 情况一:导入整个模块](#5.1 情况一:导入整个模块)
- [5.2 情况二:导入模块中的具体功能](#5.2 情况二:导入模块中的具体功能)
- [5.3 * 与 all:精准控制导入范围](#5.3 * 与 all:精准控制导入范围)
-
- [5.3.1 从模块导入所有功能](#5.3.1 从模块导入所有功能)
- [5.3.2 从包导入所有模块](#5.3.2 从包导入所有模块)
- [5.4 补充技巧:用 as 起别名](#5.4 补充技巧:用 as 起别名)
- [5.5 示例表](#5.5 示例表)
- [6. 保护测试代码:if name == "main"](#6. 保护测试代码:if name == "main")
Python 模块与包
当代码量逐渐变大时,把所有逻辑都堆在一个文件里很快就会变得难以维护。Python 提供了功能 → 模块 → 包这一套组合来帮我们组织代码。这篇文章的目标就是理清这三层概念,并彻底搞懂如何正确导入。
1. 什么是功能
定义 :功能就是一段可以完成特定任务的代码块,最典型的就是一个函数。
举例:下面的 add 函数实现了一个"加法"功能。
python
def add(a, b):
return a + b
除了函数,类、变量也可以看作模块对外提供的"功能"。
2. 什么是模块
定义 :一个扩展名为 .py 的 Python 文件就是一个模块。模块里可以包含多个函数、类、变量,它们共同完成某一类相关任务。
举例:我们创建一个文件 math_ops.py,它就是一个模块。
python
# math_ops.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
3. 什么是包
定义 :当模块越来越多时,我们可以用文件夹来组织它们。一个包含 __init__.py 文件的文件夹,就成为了一个包。
如果文件夹里没有
__init__.py,那它只是一个普通文件夹,不能被当作包来导入。
__init__.py 有什么用?
- 标识包:告诉 Python 这个目录是一个软件包。
- 初始化配置 :可以在里面写一些包级别的初始化代码,或者定义
__all__列表(后面会详细讲它的用法)。 - 简化导入:可以把常用功能提前导入到包的名字空间中,方便外部使用。
举例:假设我们有这样一个目录结构:
project/
├── main.py
└── utils/
├── __init__.py
└── string_helpers.py
这里的 utils 就是一个包,因为它包含了 init.py。string_helpers.py 是包内的一个模块。
4. 为什么要引入这些概念?
想象一下,如果把所有的函数、类都塞进一个文件里:
- 代码一长,找东西和改东西都费劲(难维护);
- 想在另一个项目里复用某个功能,只能复制粘贴(复用性差);
- 别人写的函数名可能和你写的冲突(命名冲突)。
通过 模块 和 包 来组织代码,可以让项目结构更清晰,复用更方便,还能有效避免命名冲突。
5. 如何导入:先理清"路径"与"目标"
导入时,我们只需要问自己两个问题,就能写出正确的导入语句:
- "它在哪里?" 这决定了
from后面写什么路径。 - "我要拿什么?" 这决定了
import后面是写模块名还是功能名,即我们想要获取到的目标。
关于路径的起点 :所有的路径,都是相对于当前运行文件所在的目录 作为参考。如果目标模块就在这个目录下,我们就不需要写"包名",也就是
from可以省略。如果它在某个子文件夹(包)里,我们就需要写出包名或者包名.子包名。
为了理清这部分知识,我们假设现在正编写一个文件 main.py,它的同级目录结构如下:
project/
├── main.py # 我们正在写的文件
├── calc.py # 一个顶层模块,包含 add, subtract 等功能
└── utils/ # 一个包(因为含有 __init__.py)
├── __init__.py
└── helper.py # 包里的模块,包含 clean_string, format_text 等功能
5.1 情况一:导入整个模块
当我们希望使用一个模块里的多个功能,或者想保留模块名前缀以避免命名冲突时,会选择导入整个模块。
1) 模块就在当前目录下
import 后面直接写文件名(不带 .py)。此时不需要 from,因为 main.py 和 calc.py 同级,Python 默认会在当前目录寻找。
python
import calc
# 使用时必须带上模块名前缀
calc.add(1, 2)
calc.subtract(5, 3)
2) 模块在某个包里
main.py 和 helper.py 处于不同目录级别,有两种写法:
- 写法A:
import 包名.模块名
使用时需要写出完整的路径,前缀较长,但名字冲突风险低。 - 写法B:
from 包名 import 模块名
这种方式会把模块名直接提到当前空间,使用时只需写模块名。
python
# 写法A
import utils.helper
utils.helper.clean_string(" Hello ")
# 写法B
from utils import helper
helper.clean_string(" Hello ")
5.2 情况二:导入模块中的具体功能
当我们只想用模块里的某个函数或类 时,可以直接"指名道姓"地导入。此时 from 绝对不能省略,因为必须告诉 Python 去哪个模块里找这个功能。
1) 功能来自当前目录下的模块
python
# 从同目录的 calc 模块中导入 add 函数
from calc import add
print(add(2, 3)) # 直接用功能名,无需写 calc.add
2) 功能来自包里的模块
路径要写完整:包名.模块名。
python
from utils.helper import clean_string
print(clean_string(" Hello "))
5.3 * 与 all:精准控制导入范围
有时我们想一次性导入某个模块的所有功能,或某个包的所有模块,就会用到 *。但为了避免污染当前命名空间,Python 提供了 __all__ 这个"白名单"机制。
5.3.1 从模块导入所有功能
在模块文件 (如 helper.py)中定义 __all__ 列表,from 模块 import * 就只会 导入列表里列出的名字。
未定义 __all__ 时,会导入所有不以下划线 _ 开头的名字。
python
# helper.py
__all__ = ["clean_string", "format_text"] # 只有这两个会被 * 导入
def clean_string(s):
...
def format_text(s):
...
def _internal_util(): # 下划线开头,永远不会被 * 导入
...
python
# main.py
from utils.helper import *
# 现在只能直接使用 clean_string 和 format_text
5.3.2 从包导入所有模块
在包的 __init__.py 中定义 __all__ 列表,可以控制 from 包 import * 会导入哪些模块。
python
# utils/__init__.py
__all__ = ["helper"] # 只允许导入 helper 模块
python
# main.py
from utils import *
# 现在可以直接使用 helper 模块,utils 包内的其他模块则不会被导入
helper.clean_string(" Hi ")
5.4 补充技巧:用 as 起别名
无论你导入的是模块还是功能,都可以用 as 给它取一个简短易记的别名,这在处理长路径或避免名字冲突时尤其方便。
python
import utils.helper as uh
from utils.helper import clean_string as clean
uh.clean_string(" Hi ")
clean(" Hello ")
5.5 示例表
| 目标 | 写法示例 | from (路径) |
import (目标) |
说明 |
|---|---|---|---|---|
| 整个模块(同目录) | import calc |
(省略) | 模块名 | 使用 calc.add() |
| 整个模块(包里) | from utils import helper |
包名 | 模块名 | 使用 helper.clean_string() |
| 单个功能(同目录模块) | from calc import add |
模块名 | 功能名 | from 不能省 |
| 单个功能(包里模块) | from utils.helper import clean_string |
包.模块名 | 功能名 | 路径完整,精确到模块 |
| 模块所有功能 | from helper import * |
模块名 | * |
受模块内 __all__ 控制 |
| 包所有模块 | from utils import * |
包名 | * |
受 __init__.py 内 __all__ 控制 |
| 起别名 | import calc as c |
(视情况) | 原名 as 别名 | 后续用 c.add() 调用 |
6. 保护测试代码:if name == "main"
我们常常会在模块底部写一些测试代码来验证函数。但如果这个模块被其他文件导入,我们并不希望那些测试代码运行。
解决方法是用一个特殊的变量 __name__:
- 当直接运行 该文件时,
__name__的值被 Python 自动设为"__main__"; - 当文件作为模块被导入 时,
__name__的值则是模块本身的名字(如"math_ops")。
因此,只要把测试代码放进 if __name__ == "__main__": 里面,就能保证:
直接运行 → 执行测试;被导入 → 测试代码静悄悄,不影响调用方。
python
# math_ops.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
if __name__ == "__main__":
# 这里的代码只在直接运行本文件时执行
print("测试 add:", add(3, 5))
以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。