Python(十八) 中的模块与包
一、什么是模块
在 Python 中,一个 .py 文件就可以看作一个模块。
通俗地说:
模块就是一个装有 Python 代码的文件。
比如有一个文件叫:
math_tools.py
这个文件里面写了一些函数:
def add(a, b):
return a + b
def sub(a, b):
return a - b
那么 math_tools.py 就是一个模块。
如果另一个 Python 文件想使用里面的 add() 函数,就可以导入这个模块。
模块的作用可以理解为:
把相关代码放在一个文件中。
需要使用时,再导入进来。
二、为什么要使用模块
如果所有代码都写在一个文件里,文件会越来越长,越来越难管理。
使用模块可以把代码拆开。
例如:
student.py 负责学生相关功能
score.py 负责成绩相关功能
file_tools.py 负责文件操作功能
main.py 程序入口
这样做的好处:
1. 代码结构更清楚
2. 不同功能可以分开管理
3. 代码可以重复使用
4. 多人合作时更方便
5. 程序维护和修改更容易
可以这样告诉学生:
函数是把一段代码封装起来。
模块是把一组相关函数和变量放到一个文件里。
包是把一组相关模块放到一个文件夹里。
三、import 语句的基本用法
import 的作用是导入模块。
基本格式:
import 模块名
示例:
import math
print(math.sqrt(16))
输出结果:
4.0
这里:
math 是 Python 自带的数学模块。
sqrt 是 math 模块中的平方根函数。
使用 import math 后,调用模块中的内容时要写:
math.sqrt(16)
也就是:
模块名.函数名
四、导入自己写的模块
假设当前文件夹中有两个文件:
math_tools.py
main.py
math_tools.py 内容如下:
def add(a, b):
return a + b
def sub(a, b):
return a - b
main.py 中可以这样导入:
import math_tools
result = math_tools.add(3, 5)
print(result)
输出结果:
8
解释:
import math_tools 表示导入 math_tools.py 这个模块。
math_tools.add(3, 5) 表示调用 math_tools 模块中的 add 函数。
注意:
导入模块时,不写 .py 后缀。
正确:
import math_tools
错误:
import math_tools.py
五、from...import 的用法
除了导入整个模块,也可以只导入模块中的某个函数、变量或类。
基本格式:
from 模块名 import 名字
示例:
from math import sqrt
print(sqrt(16))
输出结果:
4.0
这里导入的是 math 模块中的 sqrt 函数。
使用这种方式后,可以直接写:
sqrt(16)
不需要写:
math.sqrt(16)
再看自己写的模块。
math_tools.py:
def add(a, b):
return a + b
def sub(a, b):
return a - b
main.py:
from math_tools import add
print(add(3, 5))
这样只导入了 add,没有导入 sub。
六、import 和 from...import 的区别
两种写法都可以导入模块内容,但使用方式不同。
1. import 模块名
import math
print(math.sqrt(16))
特点:
导入整个模块。
使用时要写 模块名.函数名。
名字来源清楚,不容易冲突。
2. from 模块名 import 名字
from math import sqrt
print(sqrt(16))
特点:
只导入指定名字。
使用时可以直接写函数名。
代码更短,但名字来源不如第一种明显。
教学建议:
初学阶段更推荐 import 模块名。
因为写出模块名,代码来源更清楚。
七、导入时使用别名 as
有时模块名比较长,或者容易和其他名字冲突,可以使用 as 起别名。
基本格式:
import 模块名 as 别名
示例:
import math as m
print(m.sqrt(16))
这里的 m 就是 math 的别名。
再如,数据分析中常见写法:
import numpy as np
import pandas as pd
也可以给导入的函数起别名:
from math import sqrt as square_root
print(square_root(16))
教学提醒:
别名要清楚,不要为了短而短。
不推荐:
import math as x
因为 x 看不出代表什么。
八、一次导入多个名字
可以从一个模块中一次导入多个名字。
示例:
from math import sqrt, pi
print(sqrt(16))
print(pi)
也可以分行写,让代码更清楚:
from math import (
sqrt,
pi,
)
如果名字很多,说明这个模块可能被使用得比较多。
这时也可以考虑直接导入整个模块:
import math
print(math.sqrt(16))
print(math.pi)
九、不推荐使用 from 模块 import *
Python 支持这种写法:
from math import *
它表示导入 math 模块中很多可用的名字。
这样写后,可以直接使用:
print(sqrt(16))
print(pi)
但是教学中要提醒学生:不推荐这样写。
原因:
1. 不清楚哪些名字被导入了
2. 容易造成变量名冲突
3. 代码可读性变差
4. 后期排查问题更困难
例如:
from math import *
pi = 3
print(pi)
这里的 pi 已经被自己定义的变量覆盖了。
推荐写法:
import math
print(math.pi)
或者:
from math import pi
print(pi)
十、导入技巧与建议
1. 导入通常写在文件开头
推荐:
import math
import random
def main():
print(math.sqrt(16))
这样别人一看文件开头,就知道这个程序依赖哪些模块。
2. 标准库、第三方库、自己写的模块分组导入
推荐顺序:
import os
import sys
import requests
import math_tools
可以理解为:
第一组:Python 自带模块
第二组:第三方安装的模块
第三组:自己项目中的模块
初学阶段不必强制,但养成习惯很好。
3. 不要在模块名和文件名上覆盖标准库
不要把自己的文件命名为:
random.py
math.py
sys.py
os.py
json.py
time.py
因为这些名字和 Python 标准库模块同名。
例如,你创建了一个 random.py,然后写:
import random
Python 可能优先导入你自己的 random.py,而不是标准库中的 random 模块。
这会导致很奇怪的问题。
推荐文件名:
random_demo.py
math_tools.py
json_example.py
4. 尽量让导入关系简单
如果 A 模块导入 B,B 模块又导入 A,就可能出现循环导入问题。
这种情况叫循环导入。
示意:
a.py 导入 b.py
b.py 又导入 a.py
这会让程序变得复杂,也可能报错。
解决思路:
1. 重新整理代码结构
2. 把公共函数放到第三个模块中
3. 减少模块之间互相依赖
十一、模块搜索路径 sys.path
当我们写:
import math_tools
Python 需要知道去哪里找 math_tools 这个模块。
Python 查找模块的位置叫模块搜索路径。
这些路径保存在 sys.path 中。
可以用下面代码查看:
import sys
print(sys.path)
sys.path 是一个列表,里面保存了多个路径。
Python 导入模块时,会按照这些路径依次查找。
可以简单理解为:
Python 会拿着模块名,去 sys.path 里的路径一个一个找。
找到就导入。
找不到就报错。
常见搜索位置包括:
当前脚本所在目录
Python 标准库目录
第三方库安装目录
环境变量 PYTHONPATH 中的目录
如果找不到模块,就会报:
ModuleNotFoundError
十二、临时修改 sys.path
sys.path 是列表,所以可以临时添加路径。
示例:
import sys
sys.path.append("D:/my_project/tools")
import math_tools
这表示把 "D:/my_project/tools" 加入模块搜索路径。
然后 Python 就可以去这个目录中找模块。
但是教学时要提醒学生:
不建议在正式项目中过度依赖手动修改 sys.path。
因为这样会让代码依赖具体路径,换一台电脑可能就不能运行。
更推荐的做法:
1. 使用合理的项目结构
2. 使用包来组织代码
3. 从项目根目录运行程序
4. 必要时把项目安装成包
初学阶段只需要知道:
sys.path 决定 Python 去哪里找模块。
十三、什么是包
包,英文叫 package。
包本质上是一个文件夹。
这个文件夹里可以放多个模块。
通俗地说:
模块是一个 .py 文件。
包是装着多个模块的文件夹。
示例项目结构:
my_project/
main.py
tools/
__init__.py
math_tools.py
string_tools.py
这里:
math_tools.py 是模块。
string_tools.py 是模块。
tools 是包。
在 main.py 中可以这样导入:
from tools import math_tools
print(math_tools.add(3, 5))
也可以这样:
from tools.math_tools import add
print(add(3, 5))
十四、init.py 的作用
__init__.py 是包中的一个特殊文件。
在早期 Python 版本中,一个文件夹必须包含 __init__.py,才会被 Python 当作包。
在 Python 3.3 之后,即使没有 __init__.py,某些情况下也可以作为命名空间包使用。
但是教学和普通项目中,仍然建议保留 __init__.py。
原因:
1. 明确告诉别人这个文件夹是一个包
2. 可以在里面写包初始化代码
3. 可以控制包对外暴露哪些内容
4. 兼容性更好,结构更清楚
最简单的 __init__.py 可以是空文件。
项目结构:
tools/
__init__.py
math_tools.py
空的 __init__.py 表示:
这个文件夹是一个 Python 包。
十五、在 init.py 中简化导入
假设项目结构如下:
tools/
__init__.py
math_tools.py
math_tools.py:
def add(a, b):
return a + b
如果 __init__.py 是空的,外部可以这样用:
from tools.math_tools import add
print(add(3, 5))
也可以在 tools/__init__.py 中写:
from .math_tools import add
然后外部就可以这样导入:
from tools import add
print(add(3, 5))
这样使用起来更简洁。
但是也要注意:
__init__.py 不要写太多复杂逻辑。
它主要用于包初始化和导出常用内容。
十六、all 的作用
__all__ 可以用来控制 from 模块 import * 时导入哪些名字。
例如在 math_tools.py 中:
__all__ = ["add"]
def add(a, b):
return a + b
def sub(a, b):
return a - b
如果写:
from math_tools import *
那么只会导入 add,不会导入 sub。
不过要提醒学生:
即使有 __all__,也不推荐经常使用 import *。
__all__ 更适合在包或模块设计时,明确告诉别人:
哪些名字是希望对外使用的。
十七、绝对导入
绝对导入是从项目的顶层包开始写完整路径。
示例项目结构:
my_project/
main.py
app/
__init__.py
models.py
services.py
如果在 main.py 中导入 services.py:
from app import services
如果在 services.py 中导入 models.py:
from app import models
或者:
from app.models import Student
这种从顶层包 app 开始写的导入方式,就是绝对导入。
优点:
1. 路径清楚
2. 容易看出模块来自哪里
3. 适合较大的项目
十八、相对导入
相对导入是根据当前模块所在位置来导入其他模块。
相对导入常见符号:
. 当前包
.. 上一级包
... 上上一级包
示例项目结构:
my_project/
app/
__init__.py
models.py
services.py
如果在 services.py 中导入同一个包下的 models.py,可以写:
from . import models
也可以写:
from .models import Student
这里的 . 表示当前包 app。
再看一个上一级导入:
my_project/
app/
__init__.py
models.py
views/
__init__.py
user_view.py
如果在 user_view.py 中导入上一级包中的 models.py:
from .. import models
这里的 .. 表示上一级包。
十九、相对导入和绝对导入的区别
可以这样对比:
绝对导入:
从项目顶层包开始写路径。
例如 from app.models import Student
相对导入:
从当前模块所在位置出发写路径。
例如 from .models import Student
绝对导入的优点:
路径清楚,适合阅读。
相对导入的优点:
包内部模块互相导入时比较简洁。
教学建议:
初学阶段优先掌握绝对导入。
理解包结构后,再学习相对导入。
注意:
相对导入通常用于包内部。
不要在普通脚本直接运行时随意使用相对导入。
二十、相对导入常见错误
很多学生会遇到类似错误:
ImportError: attempted relative import with no known parent package
常见原因:
把使用相对导入的模块当作普通脚本直接运行了。
例如:
my_project/
app/
__init__.py
models.py
services.py
services.py 中写:
from .models import Student
如果直接运行:
python app/services.py
可能会报错。
更推荐从项目根目录使用模块方式运行:
python -m app.services
这样 Python 才知道 services.py 属于 app 这个包。
教学时可以简单记:
相对导入依赖包结构。
直接运行某个包内部文件,容易让 Python 找不到它的父包。
二十一、name 是什么
__name__ 是 Python 自动提供的一个特殊变量。
每个 Python 文件运行时,都会有一个 __name__。
它的值取决于这个文件是怎么被使用的。
有两种常见情况:
1. 文件被直接运行时,__name__ 的值是 "__main__"
2. 文件被当作模块导入时,__name__ 的值是模块名
示例:
创建一个文件 demo.py:
print(__name__)
如果直接运行:
python demo.py
输出:
__main__
如果在另一个文件中导入它:
import demo
那么 demo.py 中的 __name__ 通常是:
demo
二十二、name == "main" 的作用
常见写法:
if __name__ == "__main__":
main()
它的意思是:
如果当前文件是被直接运行的,就执行 main()。
如果当前文件是被导入的,就不执行 main()。
完整示例:
def add(a, b):
return a + b
def main():
result = add(3, 5)
print(result)
if __name__ == "__main__":
main()
如果直接运行这个文件:
python math_tools.py
会执行 main()。
如果在别的文件中导入:
import math_tools
不会自动执行 main()。
这样做的好处:
1. 模块可以被导入使用
2. 文件也可以单独运行测试
3. 避免导入模块时执行不该执行的代码
二十三、为什么导入模块时会执行模块代码
这是初学者很容易忽略的一点:
import 一个模块时,Python 会执行这个模块中的顶层代码。
例如 demo.py:
print("demo 模块被执行了")
def hello():
print("你好")
main.py:
import demo
运行 main.py 时,会输出:
demo 模块被执行了
原因:
导入模块时,模块中的顶层代码会执行一遍。
所以,不希望导入时自动执行的代码,应该放到:
if __name__ == "__main__":
...
里面。
推荐写法:
def hello():
print("你好")
def main():
hello()
if __name__ == "__main__":
main()
二十四、模块只会被导入执行一次
在同一个程序运行过程中,一个模块通常只会被导入执行一次。
示例:
import math
import math
import math
虽然写了三次,但 Python 不会重复加载三遍 math 模块。
原因:
Python 会把已经导入的模块缓存起来。
再次导入时,通常直接使用缓存。
缓存的模块可以在 sys.modules 中看到:
import sys
print(sys.modules)
初学阶段只需要知道:
import 模块时,模块顶层代码通常只执行一次。
二十五、常见标准库模块
Python 自带很多标准库模块,可以直接导入使用。
常见模块:
math 数学运算
random 随机数
datetime 日期和时间
os 操作系统相关
sys Python 解释器相关
json JSON 数据处理
pathlib 路径处理
time 时间相关
示例:
import random
num = random.randint(1, 10)
print(num)
示例:
from datetime import datetime
now = datetime.now()
print(now)
教学时可以告诉学生:
标准库就是 Python 自带的工具箱。
很多常见功能不用自己从零写,可以先看看标准库有没有。
二十六、第三方模块
除了 Python 自带的标准库,还有很多第三方模块。
第三方模块通常需要先安装,再导入。
例如:
requests 用于发送网络请求
numpy 用于科学计算
pandas 用于数据分析
flask 用于 Web 开发
安装第三方模块通常使用:
pip install 模块名
例如:
pip install requests
安装后在 Python 中导入:
import requests
注意:
import 是导入模块。
pip install 是安装模块。
很多初学者会混淆这两个概念。
可以这样讲:
pip install 像是把工具买回来。
import 像是把工具拿出来使用。
二十七、常见错误 1:ModuleNotFoundError
错误示例:
import my_tools
如果 Python 找不到 my_tools,就会报:
ModuleNotFoundError: No module named 'my_tools'
常见原因:
1. 模块文件不存在
2. 模块名写错了
3. 模块不在 sys.path 搜索路径中
4. 第三方模块没有安装
5. 当前运行目录不对
解决思路:
1. 检查文件名
2. 检查导入路径
3. 检查运行位置
4. 检查是否安装了第三方库
5. 查看 sys.path
二十八、常见错误 2:文件名和模块名冲突
假设你创建了一个文件:
random.py
然后里面写:
import random
print(random.randint(1, 10))
这可能会出问题,因为你自己的文件名也叫 random.py。
Python 可能导入当前文件,而不是标准库中的 random。
解决办法:
不要把自己的文件命名为标准库模块名。
推荐改成:
random_demo.py
同样要避免:
math.py
sys.py
os.py
json.py
time.py
datetime.py
二十九、常见错误 3:循环导入
循环导入指两个模块互相导入。
示例:
a.py:
import b
def func_a():
print("A")
b.py:
import a
def func_b():
print("B")
这可能造成导入时机混乱,导致某些名字还没定义就被使用。
解决思路:
1. 把公共代码提取到第三个模块
2. 减少模块之间互相依赖
3. 只在需要时再局部导入
4. 重新设计项目结构
例如:
a.py 和 b.py 都需要的函数,可以放到 common.py。
结构:
common.py
a.py
b.py
这样 a.py 和 b.py 都导入 common.py,不再互相导入。
三十、常见错误 4:导入路径和运行位置不一致
同一段代码,在不同目录下运行,导入结果可能不同。
例如项目结构:
my_project/
main.py
tools/
__init__.py
math_tools.py
如果你在 my_project 目录下运行:
python main.py
导入可能正常。
但如果你在其他目录下运行,Python 的搜索路径可能不同,就可能找不到模块。
教学建议:
运行项目时,尽量从项目根目录运行。
对于包内部模块,可以使用:
python -m 包名.模块名
例如:
python -m app.services
三十一、推荐的简单项目结构
初学阶段可以使用这样的结构:
student_system/
main.py
student.py
score.py
file_tools.py
当文件越来越多时,可以使用包:
student_system/
main.py
services/
__init__.py
student_service.py
score_service.py
utils/
__init__.py
file_tools.py
在 main.py 中导入:
from services import student_service
from utils import file_tools
或者:
from services.student_service import add_student
from utils.file_tools import save_data
教学提醒:
项目结构不是越复杂越好。
代码少时,一个或几个文件就够。
代码变多后,再考虑拆分模块和包。
三十二、课堂示例
示例 1:导入标准库模块
import random
num = random.randint(1, 100)
print("随机数是:", num)
讲解重点:
random 是 Python 标准库模块。
randint 可以生成指定范围内的随机整数。
示例 2:导入自己写的模块
math_tools.py:
def add(a, b):
return a + b
main.py:
import math_tools
result = math_tools.add(3, 5)
print(result)
讲解重点:
一个 .py 文件就是一个模块。
导入自己写的模块时,不需要写 .py 后缀。
示例 3:使用 from...import
from math import sqrt
print(sqrt(25))
讲解重点:
from math import sqrt 表示只导入 math 模块中的 sqrt。
使用时可以直接写 sqrt()。
示例 4:创建一个包
项目结构:
my_project/
main.py
tools/
__init__.py
math_tools.py
tools/math_tools.py:
def add(a, b):
return a + b
main.py:
from tools.math_tools import add
print(add(3, 5))
讲解重点:
tools 是包。
math_tools.py 是包中的模块。
示例 5:使用 name == "main"
def add(a, b):
return a + b
def main():
print(add(3, 5))
if __name__ == "__main__":
main()
讲解重点:
文件直接运行时执行 main()。
文件被导入时不自动执行 main()。
三十三、课堂练习
练习 1:导入 math 模块
要求:使用 math 模块计算 9 的平方根。
参考代码:
import math
print(math.sqrt(9))
练习 2:编写自己的模块
要求:创建 calc.py,里面写 add() 和 sub(),再在 main.py 中导入使用。
参考代码:
calc.py:
def add(a, b):
return a + b
def sub(a, b):
return a - b
main.py:
import calc
print(calc.add(10, 3))
print(calc.sub(10, 3))
练习 3:使用包组织代码
要求:创建如下结构:
my_project/
main.py
tools/
__init__.py
string_tools.py
string_tools.py 中写一个函数:
def to_upper(text):
return text.upper()
main.py 中导入并使用:
from tools.string_tools import to_upper
print(to_upper("python"))
练习 4:观察 name
要求:创建 demo.py,观察直接运行和导入时 __name__ 的区别。
demo.py:
print("__name__ 的值是:", __name__)
直接运行:
python demo.py
再创建 main.py:
import demo
运行:
python main.py
观察两次输出有什么不同。
练习 5:给模块添加 main()
要求:让模块既可以被导入,也可以单独运行测试。
参考代码:
def add(a, b):
return a + b
def main():
print(add(1, 2))
if __name__ == "__main__":
main()
三十四、教学建议
讲解模块与包时,可以按照下面顺序:
1. 先说明一个 .py 文件就是一个模块
2. 再说明为什么代码多了要拆分文件
3. 讲 import 模块名
4. 讲 from 模块 import 名字
5. 讲 as 别名和不推荐 import *
6. 讲 sys.path:Python 到哪里找模块
7. 讲包:文件夹 + 多个模块
8. 讲 __init__.py 的作用
9. 讲绝对导入和相对导入
10. 讲 __name__ == "__main__"
11. 最后讲常见错误和项目结构
可以用下面的问题引导学生:
如果代码越来越多,都写在一个文件里会怎样?
一个 .py 文件能不能被另一个 .py 文件使用?
Python 怎么知道去哪里找模块?
文件夹为什么能变成包?
为什么导入模块时,有些代码会自动执行?
__name__ == "__main__" 到底是在防止什么?
教学重点建议放在:
import 的基本用法
from...import 的基本用法
模块名不要加 .py
sys.path 的概念
包和 __init__.py
绝对导入
相对导入的使用场景
__name__ == "__main__"
常见导入错误
三十五、总结
模块与包是 Python 组织代码的重要方式。
可以这样记:
模块:一个 .py 文件
包:装有多个模块的文件夹
__init__.py:包中的特殊文件,用来标识和初始化包
import:导入模块
sys.path:Python 查找模块的路径列表
常见导入方式:
import math
from math import sqrt
import math as m
from tools.math_tools import add
绝对导入和相对导入:
绝对导入:
从项目顶层包开始写路径。
相对导入:
从当前模块所在位置出发写路径。
__name__ == "__main__" 的作用:
让文件直接运行时执行某些代码。
让文件被导入时不自动执行这些代码。
最重要的注意事项:
1. 导入模块时不写 .py 后缀
2. 不推荐 from 模块 import *
3. 不要把自己的文件命名为 random.py、math.py、sys.py 等标准库名字
4. Python 按 sys.path 查找模块
5. 包通常建议保留 __init__.py
6. 相对导入通常用于包内部
7. 导入模块时,模块顶层代码会执行
8. 用 __name__ == "__main__" 避免导入时执行测试代码
一句话总结:
模块和包让 Python 程序可以按功能拆分和组织,import 让不同文件之间可以互相使用代码。