一、回顾与本篇目标
上一篇我们学了函数,你学会了把一段逻辑封装起来,起个名字,随叫随到。但这只解决了“代码复用”的一半问题——在同一个文件里复用。
如果另一个项目也需要用你写的工具函数怎么办?复制粘贴?那迟早会出问题——改了这头的代码忘了改那头,两个版本渐行渐远。真正的解决办法是模块:把函数、类、变量放在独立的文件里,哪个项目需要就导入哪个。
本篇的目标:
- 理解 Python 中模块和包的概念
- 学会
import的四种导入方式 - 掌握
if __name__ == '__main__'的作用 - 学会用 pip 安装第三方库
- 学会用 venv 创建虚拟环境
二、什么是模块
在 Python 中,一个 .py 文件就是一个模块。文件名(去掉 .py)就是模块名。这件事极其简单——你不需要像 JavaScript 那样写 module.exports 或 export。文件里定义的所有函数、变量、类,默认都可以被其他文件导入。
假设你有一个文件 math_utils.py:
# math_utils.py —— 数学工具模块
PI = 3.14159
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def circle_area(radius):
return PI * radius ** 2
在另一个文件 main.py(和 math_utils.py 在同一文件夹)里,你可以导入并使用它:
# main.py
import math_utils
print(math_utils.PI) # 3.14159
print(math_utils.add(10, 20)) # 30
print(math_utils.circle_area(5)) # 78.53975
这就是模块最基本的用法。不需要任何声明、不需要任何配置文件。Python 的设计哲学在这里体现得淋漓尽致:简单到几乎没有学习成本。
三、import 的四种方式
Python 提供了多种导入方式,适用于不同场景。
方式一:导入整个模块
import math_utils
# 使用时需要带模块名前缀
print(math_utils.add(3, 5))
这是最推荐的方式。优点:清晰——一眼就能看出 add 函数来自哪个模块;不会和当前文件里的同名函数冲突。
方式二:导入模块中的特定函数
from math_utils import add, circle_area
# 使用时不需要模块名前缀
print(add(3, 5))
print(circle_area(5))
优点:简洁。缺点:如果当前文件也有一个叫 add 的函数,就会覆盖掉导入的那个。
方式三:导入模块中的所有内容(不推荐)
from math_utils import *
print(add(3, 5))
print(PI)
这会把模块里所有不以 _ 开头的名字全部导入当前命名空间。强烈不推荐,因为它会让你搞不清哪些名字是从外部来的,容易造成命名冲突。
方式四:导入时给模块或函数起别名
import math_utils as mu
print(mu.add(3, 5))
# 也可以给函数起别名
from math_utils import circle_area as ca
print(ca(5))
当模块名很长时,起别名很有用。比如 import numpy as np 是数据科学领域的标准写法。
和 JavaScript 的对比
| 操作 | JavaScript (ES6) | JavaScript (CommonJS) | Python |
|---|---|---|---|
| 导出 | export function foo() |
module.exports = {foo} |
默认所有顶层名称都可导入 |
| 导入整个模块 | import * as utils from './utils' |
const utils = require('./utils') |
import utils |
| 导入特定内容 | import { foo } from './utils' |
const { foo } = require('./utils') |
from utils import foo |
| 起别名 | import { foo as f } |
const f = require('./utils').foo |
from utils import foo as f |
四、Python 查找模块的路径
当你写 import math_utils 时,Python 怎么知道去哪找这个文件?它会按顺序搜索以下路径:
- 当前文件所在的目录
- PYTHONPATH 环境变量中的目录
- Python 标准库的目录
- 第三方库安装的目录(site-packages)
你可以查看当前 Python 的搜索路径:
import sys
for path in sys.path:
print(path)
如果导入失败(ModuleNotFoundError),先检查文件名对不对,再检查文件是否在搜索路径里。
五、if __name__ == '__main__':让文件有两种运行模式
这是 Python 中最经典的两行代码,几乎每个 Python 文件底部都能看到:
if __name__ == '__main__':
# 这里的代码只在直接运行这个文件时执行
# 被其他文件 import 时不会执行
pass
它解决什么问题?
假设 math_utils.py 底部有一些测试代码:
# math_utils.py
def add(a, b):
return a + b
# 测试代码
print(add(10, 20))
当你写 import math_utils 时,整个 math_utils.py 文件会被执行一遍——包括那行 print。这意味着每次导入这个模块,都会在控制台打印一次测试结果,这显然不是你想要的行为。
但你又希望在直接运行 math_utils.py 时能执行这些测试。这就需要区分两种运行模式。
Python 通过 __name__ 变量来区分:
- 当你直接运行一个 Python 文件时(如
python math_utils.py),该文件的__name__变量被设置为'__main__'。 - 当这个文件被作为模块导入时(
import math_utils),__name__被设置为模块名'math_utils'。
所以,if __name__ == '__main__' 的意思是:只有当这个文件被直接运行时,才执行下面的代码。
# math_utils.py —— 正确的写法
PI = 3.14159
def add(a, b):
return a + b
def circle_area(radius):
return PI * radius ** 2
# 测试代码放在这里
if __name__ == '__main__':
print('=== 测试 math_utils 模块 ===')
print(f'add(10, 20) = {add(10, 20)}')
print(f'circle_area(5) = {circle_area(5)}')
现在:
- 直接运行
python math_utils.py:会输出测试结果。 - 在其他文件中导入
import math_utils:不会输出任何东西,只导入函数。
这是一个非常优雅的设计。每个 Python 文件都可以同时是“可执行的脚本”和“可导入的模块”。
六、包:把多个模块组织成一个文件夹
当模块越来越多,你会想把相关的模块放在同一个文件夹里。这个文件夹就是一个包。
一个包在文件系统中就是一个包含 __init__.py 文件的文件夹。这个 __init__.py 可以是空的,它只是告诉 Python:“把这个文件夹当作一个包来对待”。
my_project/
├── main.py
└── utils/
├── __init__.py ← 让 utils 成为一个包
├── math_utils.py ← 数学工具
└── string_utils.py ← 字符串工具
使用包中的模块:
# main.py
# 方式一:导入包中的模块
import utils.math_utils
print(utils.math_utils.add(3, 5))
# 方式二:从包中导入模块
from utils import math_utils
print(math_utils.add(3, 5))
# 方式三:从包中的模块导入函数
from utils.math_utils import add
print(add(3, 5))
和 JavaScript 中从 node_modules 导入第三方模块类似,只不过 Python 的包就是普通的文件夹,不需要注册到 package.json。
七、pip:安装第三方库
Python 自带的标准库非常强大(math、random、json、datetime、os 等),但真正的生态力量在第三方库——全世界开发者贡献的开源模块。
pip 是 Python 的包管理工具,相当于 Node.js 的 npm。它让你用一行命令安装任何第三方库。
检查 pip 是否已安装
pip --version
如果显示版本号,说明 pip 已就绪。pip 通常随 Python 一起安装。
安装第三方库
pip install 库名
例如安装 requests(最常用的 HTTP 请求库):
pip install requests
安装后,就可以在代码中导入使用了:
import requests
response = requests.get('https://www.example.com')
print(response.status_code)
print(response.text[:200])
pip 常用命令
| 命令 | 作用 | npm 对应命令 |
|---|---|---|
pip install 库名 |
安装一个库 | npm install 库名 |
pip install 库名==版本号 |
安装指定版本 | npm install 库名@版本号 |
pip install --upgrade 库名 |
升级一个库 | npm update 库名 |
pip uninstall 库名 |
卸载一个库 | npm uninstall 库名 |
pip list |
列出所有已安装的库 | npm list -g --depth=0 |
pip freeze > requirements.txt |
导出依赖列表 | npm shrinkwrap 或 package.json |
pip install -r requirements.txt |
从依赖列表安装 | npm install |
Python 项目的依赖通常记录在 requirements.txt 文件中,相当于 Node.js 的 package.json:
# requirements.txt
requests==2.31.0
flask==3.0.0
pymysql==1.1.0
别人拿到这个文件后,执行 pip install -r requirements.txt 就能安装完全相同的依赖版本。
八、虚拟环境:让每个项目有独立的 Python 环境
这是一个非常重要的概念。假设你有两个项目:
- 项目 A 需要 requests 版本 2.28.0
- 项目 B 需要 requests 版本 2.31.0
如果你把两个项目的依赖都装在系统全局的 Python 里,版本就会冲突。虚拟环境解决了这个问题:为每个项目创建一个隔离的 Python 环境,各装各的依赖,互不影响。
Python 内置了 venv 模块来创建虚拟环境。
创建虚拟环境
# 在项目根目录执行
python -m venv venv
这会在当前文件夹创建一个名为 venv 的子文件夹(你也可以起别的名字,但 venv 是惯例),里面有一份独立的 Python 解释器副本和独立的 pip。
项目文件夹结构:
my_project/
├── venv/ ← 虚拟环境(不要手动修改)
├── main.py
├── utils/
└── requirements.txt
激活虚拟环境
Windows(命令提示符或 PowerShell):
venv\Scripts\activate
Mac / Linux:
source venv/bin/activate
激活成功后,终端提示符前面会出现 (venv),表示当前在虚拟环境中:
(venv) C:\my_project>
现在你执行的 python 和 pip 都是虚拟环境里的版本。用 pip install 安装的库只会装在这个虚拟环境里,不影响系统全局 Python。
退出虚拟环境
deactivate
终端提示符前面的 (venv) 消失,回到了全局环境。
为什么要用虚拟环境(和 Node.js 的对比)
Node.js 默认就是把依赖装在项目本地的 node_modules 里,天然隔离。Python 的 pip 默认是全局安装,所以需要虚拟环境来手动隔离。这是 Python 和 Node.js 在包管理上最大的设计差异。理解了这一点,你就不会奇怪为什么 Python 项目总要多一个激活虚拟环境的步骤。
最佳实践:每新建一个 Python 项目,第一步就是创建虚拟环境、激活它、然后在里面安装依赖。别忘了把 venv 文件夹加到 .gitignore 中——这和 node_modules 不应该提交到 Git 是一个道理。
九、常用标准库一览
Python 自带的标准库非常丰富,很多常用功能不需要安装第三方库就能完成。下面列出最常用的几个:
| 模块 | 作用 | 示例 |
|---|---|---|
math |
数学函数 | math.sqrt(16)、math.pi |
random |
生成随机数 | random.randint(1, 10)、random.choice(list) |
json |
处理 JSON 数据 | json.loads(str)、json.dumps(obj) |
datetime |
处理日期和时间 | datetime.datetime.now() |
os |
操作系统相关操作 | os.path.join()、os.listdir() |
sys |
Python 解释器相关 | sys.argv(命令行参数)、sys.path |
re |
正则表达式 | re.search(pattern, text) |
collections |
高级数据结构 | Counter、defaultdict、OrderedDict |
十、综合演示:搭建一个完整的项目结构
下面用本篇学到的模块、包、虚拟环境知识,搭建一个标准的小项目。
# 项目结构
# number_tools/
# ├── venv/
# ├── main.py
# ├── requirements.txt
# └── my_utils/
# ├── __init__.py
# ├── stats.py
# └── converter.py
my_utils/__init__.py(空文件,让 my_utils 成为包)
my_utils/stats.py:
# 统计工具模块
def mean(numbers):
"""计算平均值"""
if len(numbers) == 0:
return 0
return sum(numbers) / len(numbers)
def median(numbers):
"""计算中位数"""
sorted_nums = sorted(numbers)
n = len(sorted_nums)
if n == 0:
return 0
mid = n // 2
if n % 2 == 0:
return (sorted_nums[mid - 1] + sorted_nums[mid]) / 2
else:
return sorted_nums[mid]
def mode(numbers):
"""找出众数(出现次数最多的数)"""
if len(numbers) == 0:
return None
from collections import Counter
counter = Counter(numbers)
return counter.most_common(1)[0][0]
my_utils/converter.py:
# 单位转换模块
def celsius_to_fahrenheit(celsius):
"""摄氏度转华氏度"""
return celsius * 9 / 5 + 32
def fahrenheit_to_celsius(fahrenheit):
"""华氏度转摄氏度"""
return (fahrenheit - 32) * 5 / 9
def km_to_miles(km):
"""公里转英里"""
return km * 0.621371
def miles_to_km(miles):
"""英里转公里"""
return miles / 0.621371
main.py:
# 主程序
from my_utils.stats import mean, median
from my_utils import converter
def main():
# 测试统计工具
scores = [85, 92, 78, 95, 88, 85, 70]
print('===== 成绩统计 =====')
print(f'原始数据:{scores}')
print(f'平均分:{mean(scores):.1f}')
print(f'中位数:{median(scores)}')
# 测试单位转换
print('\n===== 单位转换 =====')
celsius = 30
print(f'{celsius}°C = {converter.celsius_to_fahrenheit(celsius):.1f}°F')
km = 10
print(f'{km} 公里 = {converter.km_to_miles(km):.2f} 英里')
if __name__ == '__main__':
main()
requirements.txt(目前没有第三方依赖,可以记录标准库以外的内容):
# 本项目暂无第三方依赖
# 后续如果需要,用 pip freeze > requirements.txt 生成
这个项目结构清晰、模块职责明确、可复用性高。这就是 Python 社区推荐的标准项目组织方式。
十一、本篇动手练习
练习 1:创建自己的工具包
把前几篇练习中写的函数(计算器、温度转换、字符串处理等)整理成一个包 my_toolkit,包含两个模块:math_tools.py 和 string_tools.py。然后写一个 main.py 导入并使用它们。
练习 2:使用第三方库
新建一个项目,创建虚拟环境并激活。安装 requests 库,写一个脚本获取任意一个公开 API 的数据(比如 https://api.github.com),并打印返回的状态码和部分内容。
练习 3:练习 if __name__ == '__main__'
创建一个模块 greetings.py,里面定义一个 greet(name) 函数。模块底部用 if __name__ == '__main__' 包裹一段测试代码。然后分别测试:直接运行这个文件,和在另一个文件中导入它,观察测试代码是否执行。
十二、本篇小结
这一篇你学会了 Python 的模块和包系统:
- 模块:一个
.py文件就是一个模块。文件名即模块名。不需要export,所有顶层定义默认可导入。 - import 的四种方式:导入整个模块(推荐)、导入特定函数、导入全部(不推荐)、起别名。
if __name__ == '__main__':让一个文件既可以作为模块被导入,也可以作为脚本直接运行。直接运行时__name__是'__main__',被导入时是模块名。- 包:包含
__init__.py的文件夹。用于组织多个相关模块。 - pip:Python 的包管理工具。安装、升级、卸载第三方库。
requirements.txt记录项目依赖。 - 虚拟环境 venv:为每个项目创建独立的 Python 环境,防止依赖冲突。和 Node.js 的
node_modules隔离思想相同,但需要手动激活。
模块化是编程的基石。学会合理拆分模块、组织包结构、管理项目依赖,你就能写出结构清晰、易于维护的项目。
下一篇预告
下一篇——《文件读写——处理文本和 JSON》:学习用 Python 读写本地文件。包括文本文件的读写、with 语句自动关闭文件、JSON 文件的读写、CSV 文件的处理。这是 Python 自动化脚本和数据处理的必备技能。
Python 零基础入门,每周更新。














暂无评论内容