目 录CONTENT

文章目录

Python(十八) 中的模块与包

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.pyb.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 让不同文件之间可以互相使用代码。
0
博主关闭了当前页面的评论