前言
前三篇我们一路从环境搭建与数据类型、运算符与条件判断学到了面向对象编程基础。你写的代码越来越长、功能越来越多——于是问题来了:所有代码都塞在一个文件里,动辄上千行,该怎么管?
答案就是模块(Module) 。模块让你把代码拆分到多个文件中,各自独立、按需复用。Python 标准库就是由数百个模块组成的,学会模块机制,你才算真正掌握了 Python 的"拼图"方式。
一、模块是什么?
一个.py文件就是一个模块。文件名去掉 .py 后缀就是模块名。
my_project/├── main.py # 入口文件├── calculator.py # 模块:计算器└── utils.py # 模块:工具函数
在 calculator.py 中写一些函数:
# calculator.py"""计算器模块 —— 提供基本的数学运算"""def add(a, b): return a + bdef subtract(a, b): return a - bdef multiply(a, b): return a * bdef divide(a, b): if b == 0: raise ValueError("除数不能为零") return a / b# 模块级常量PI = 3.14159
在 main.py 中使用它:
# main.pyimport calculatorprint(calculator.add(10, 5)) # 15print(calculator.PI) # 3.14159
就是这么简单——import 模块名,然后用 模块名.函数名() 调用。模块让代码组织变得自然:一个文件负责一类功能。
二、import 的四种写法
Python 的导入语法非常灵活,四种写法应付不同场景:
2.1 最基础:import 模块名
import mathprint(math.sqrt(16)) # 4.0print(math.pi) # 3.141592653589793
每次使用都要带 模块名. 前缀,清晰明了,不会污染当前命名空间。
2.2 起别名:import 模块名 as 别名
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltarr = np.array([1, 2, 3])data = pd.DataFrame({"a": [1, 2, 3]})plt.plot([1, 2, 3], [4, 5, 6])
社区有约定俗成的别名:numpy → np,pandas → pd,matplotlib.pyplot → plt。跟着约定走,别人一看就懂。
2.3 按需导入:from 模块 import 名字
from math import sqrt, pi, cosprint(sqrt(25)) # 5.0 —— 直接写函数名,不用加 math.print(pi) # 3.141592653589793print(cos(0)) # 1.0
好处是代码更简洁,坏处是可能和当前文件里的函数重名。使用时注意避让。
2.4 全量导入(谨慎使用):from 模块 import *
from math import *print(sin(0)) # 0.0print(e) # 2.718281828459045
不推荐! 它会把这个模块里所有公开的名字全部塞进当前命名空间,你根本不知道导入了什么,极易造成命名冲突。只在极少数场景(如 tkinter 的常量定义)用到。
三、if __name__ == "__main__" 到底什么意思?
这是 Python 面试必考题,也是每个 Python 文件都该有的"入口守门员"。
3.1 先看现象
创建 greet.py:
# greet.pydef hello(name): return f"你好,{name}!"print(f"greet.py 的 __name__ 是:{__name__}")if __name__ == "__main__": print("我作为主程序运行") print(hello("世界"))
直接运行它:
$ python greet.pygreet.py 的 __name__ 是:__main__我作为主程序运行你好,世界!
再创建 app.py 导入它:
# app.pyimport greetprint("app.py 结束")
$ python app.pygreet.py 的 __name__ 是:greetapp.py 结束
3.2 规则很简单
所以 if __name__ == "__main__": 的意思是:这个文件只有当被直接运行时才执行这段代码,被导入时跳过。
3.3 为什么需要它?
# 不好 —— 每次导入都会执行print("模块加载中...")run_tests() # 别人 import 时也会跑测试!# 好 —— 只在直接运行时执行if __name__ == "__main__": print("模块加载中...") run_tests() # 只有 python xxx.py 时才跑
建议:每个 .py 文件都加上 if __name__ == "__main__":,哪怕里面暂时只有一行 pass。这是一个好习惯,日后扩展时才不会踩坑。
四、模块搜索路径:Python 怎么找到你的模块?
当你写 import something,Python 会按顺序在以下路径中搜索:
import sysfor p in sys.path: print(p)
典型输出:
(当前脚本所在目录)/usr/lib/python3.10/usr/lib/python3.10/lib-dynload/home/user/miniconda3/envs/myproject/lib/python3.10/site-packages
搜索顺序:
如果模块找不到,先检查这几处。常见解决方案:
# 临时添加搜索路径(不推荐,但应急可用)import syssys.path.append("/path/to/your/module")
更好的做法是正确组织项目结构,用包来管理。
五、包(Package):组织模块的文件夹
模块多了怎么办?用包——包含 __init__.py 的文件夹就是包。
5.1 包的基本结构
my_project/├── main.py├── mathlib/ # 这是一个包│ ├── __init__.py # 标识此文件夹为包(可为空)│ ├── arithmetic.py # 子模块:四则运算│ └── advanced.py # 子模块:高级运算└── utils/ # 另一个包 ├── __init__.py ├── fileio.py └── logger.py
__init__.py 是包的身份证。Python 3.3 起虽然没有 __init__.py 也不报错(称为"命名空间包"),但建议始终放一个,即使是空文件,明确表示"这是一个包"。
5.2 导入包中的模块
# 方式一:导入完整路径import mathlib.arithmeticprint(mathlib.arithmetic.add(3, 4))# 方式二:from 导入from mathlib.arithmetic import add, subtractprint(add(3, 4))# 方式三:from 包导入模块from mathlib import advancedprint(advanced.power(2, 10))# 方式四:别名import mathlib.advanced as advprint(adv.sqrt(16))
5.3 用 init.py 统一出口
__init__.py 不只是标识,还能控制 from 包 import * 的行为和简化接口:
# mathlib/__init__.pyfrom .arithmetic import add, subtract, multiply, dividefrom .advanced import power, sqrt, factorial__all__ = ["add", "subtract", "multiply", "divide", "power", "sqrt", "factorial"]
这样外部使用变得非常简洁:
from mathlib import add, powerprint(add(1, 2)) # 3print(power(2, 8)) # 256
5.4 相对导入:包内部互相引用
在包内部,子模块之间引用用相对导入:
# mathlib/advanced.pyfrom .arithmetic import multiply # . 表示"当前包"def power(base, exp): """用 multiply 实现乘方""" result = 1 for _ in range(exp): result = multiply(result, base) return result# mathlib/arithmetic.py 中引用上级同级from . import advanced # 导入同一个包下的 advancedfrom ..utils import logger # .. 表示上级包
相对导入规则速查:
| |
|---|
. | |
.. | |
... | |
.module | |
..package.module | |
注意:相对导入只能在包内部使用。如果直接运行一个使用了相对导入的 .py 文件(如 python arithmetic.py),会报错 ImportError: attempted relative import with no known parent package。此时需要从包外部以模块方式运行(python -m mathlib.arithmetic)。
六、常用标准库速览
Python 自带了大量实用模块,下面是最常用的一批:
| | |
|---|
os | | os.path.join("a", "b") |
sys | | sys.argv[1] |
math | | math.sqrt(16) |
random | | random.randint(1, 10) |
datetime | | datetime.datetime.now() |
json | | json.loads('{"a": 1}') |
re | | re.search(r"\d+", "abc123") |
os.path | | os.path.exists("/tmp/test.txt") |
shutil | | shutil.copy("a.txt", "b.txt") |
argparse | | |
typing | | def f(x: int) -> str: |
# 综合示例:5 行代码创建一个带时间戳的文件import jsonfrom datetime import datetimedata = {"name": "测试", "timestamp": datetime.now().isoformat(), "value": 42}with open("output.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)print("文件已生成:output.json")
七、第三方包与 pip
Python 的"标准库"只是开胃菜,真正的盛宴在 PyPI(Python Package Index),上面的第三方包数量超过 40 万。
7.1 基础操作
# 安装pip install requests# 安装指定版本pip install numpy==1.24.0# 卸载pip uninstall requests# 查看已安装的包pip list# 导出依赖列表pip freeze > requirements.txt# 根据依赖列表批量安装pip install -r requirements.txt
国内用户建议使用清华镜像加速,详见本系列第一篇的 1.4 节。
7.2 新手必知的几个第三方包
| |
|---|
requests | HTTP 请求,比标准库的 urllib 好用百倍 |
numpy | |
pandas | |
matplotlib | |
pillow | |
flask | |
7.3 靠谱的项目依赖方案
# 用虚拟环境隔离依赖(第一篇讲过)conda create -n myproject python=3.10 -yconda activate myproject# 安装依赖pip install requests pandas matplotlib# 锁定版本pip freeze > requirements.txt
requirements.txt 示例:
numpy==1.24.0pandas==2.1.0matplotlib==3.7.1requests==2.31.0
把 requirements.txt 放在项目根目录,其他人 pip install -r requirements.txt 就能复现你的环境。
八、动手练习
- 1. 创建一个
string_utils.py 模块,包含 reverse_str(text)(反转字符串)和 count_words(text)(统计单词数)两个函数。在 main.py 中导入并使用。 - 2. 建一个包
shapes,包含 circle.py(面积、周长)和 rectangle.py(面积、周长)。在 __init__.py 中统一导出接口,然后在 main.py 中用 from shapes import circle_area, rect_area 的方式调用。 - 3. 在练习 2 的包中,让
circle.py 和 rectangle.py 使用 if __name__ == "__main__": 各自包含一段测试代码。 - 4. 安装
requests 包,写一个脚本请求 https://api.github.com,打印返回的 JSON 中 current_user_url 字段的值。
九、小结
本篇我们从"一个文件就是一个模块"出发,系统掌握了 Python 的模块与包体系:
- • import 的四种写法:
import、import...as、from...import、from...import *,各有适用场景。 - •
__name__魔法:区分"直接运行"与"被导入",每个文件都该加上 if __name__ == "__main__":。 - • 模块搜索路径:
sys.path 决定 Python 在哪找模块,理解它就不会再困惑"为什么找不到模块"。 - • 包:用文件夹 +
__init__.py 组织模块,相对导入让包内部互相引用,__init__.py 统一对外接口。 - • 标准库与第三方包:
os、sys、json、datetime 等开箱即用,pip install 打开更广阔的世界。
模块是 Python 代码复用的基础单元。掌握了模块,你就能写出"分而治之"的干净代码,而不是一个几千行的巨型文件。
本文首发于微信公众号「羽您码上聊」,欢迎关注获取更多技术内容。