8  模块

一个.py文件,就是一个模块module,里面的函数、变量等等,可以被其他代码所调用。 (一个包含多个.py文件的文件夹通常称为“包(package)”,下文单独说明)

写任何一个程序之前,我们一般会尽量调用别人写好的代码(别人写好的不用白不用),这些代码一般也是用模块的形式组织。包括我们后面主要用到的数据分析三件套。

8.1 引入模块

Python作为一个发展了很多年的流行的编程语言,自带非常多的模块。我们安装的Anaconda,也格外把很多数据分析、科学计算等等的模块打包在一起。

例如用于数学运算的math,这是Python自带的模块,其中有大量数学函数(顾名思义),例如开平方根sqrt就在其中。

8.1.1 import 语句

引入模块也很简单

import <模块名>
import <模块名> as <模块的简称>

注意:只要import一次,即可一直使用,因此一般我们可以放在代码的最前面。

我们可以用<模块名>.<函数名/变量名>来调用里面的函数或者变量,例如我们要调用math模块下的平方根sqrt()

此时,模块内部的所有东西(函数、变量等),都必须通过<模块名>.来调用。

import math # 引入模块
print(math.sqrt(4)) # 调用模块内部的一个函数sqrt()
2.0

或者math中的圆周率。

print(math.pi)
3.141592653589793

也可以采用简称或者缩写,比如我们后面用到的NumPy,习惯上缩写成np

import numpy as np 
print(np.sqrt(4)) # NumPy也有自己的开方函数!后面会讲
2.0

8.1.2 引入到当前命名空间

这种方法让模块内部的函数或者变量名,直接出现在当前命名空间中(调用时不用再挂着模块的名字)。如果这个模块下的某些东西特别常用,这样可以少打一些字。

from <模块名> import <项目名>
from <模块名> import <项目名> as <项目别名>

还是引入math中的开平方函数sqrt,可能你的代码中使用特别多,所以懒得打math.sqrt(),只想打sqrt()

同样,import一次,后续即可一直使用。

from math import sqrt
print(sqrt(9))
print(sqrt(4))
3.0
2.0

还可以一次性引入模块下的所有名字

from <模块名> import *

这样你引用模块下的所有对象,都不必通过模块名来调用。

注意: 这种方式一般不推荐。一个模块里面可能有大量函数和子模块。除非你很明白自己在做什么,否则引入大量你可能用不到、甚至不知道存在的名称,容易产生命名冲突或掩盖已有名称,降低可读性与可维护性。

8.2 Python部分常用模块

注意,这里只是常用模块的冰山一角。除了一些顾名思义的函数比如sqrt(),一般是不用刻意记忆。

重要:如何找到合适的模块和函数?

  1. 问搜索引擎或者AI:Python如何开方?
  2. 问Python
import math
help(math)       # 查看math模块的帮助文档
help(math.sqrt)  # 查看math.sqrt函数的帮助文档

帮助文档很长,各位可以自行尝试。

8.2.1 math

常用的数学模块

# 部分math模块的函数
import math

# math.ceil(x): 返回大于或等于 x 的最小整数。
print(math.ceil(4.2))  # 输出 5

# math.floor(x): 返回小于或等于 x 的最大整数。
print(math.floor(4.8))  # 输出 4

# math.fabs(x): 返回 x 的绝对值。
print(math.fabs(-5))  # 输出 5.0

# math.exp(x): 返回 e^x。
print(math.exp(1))  # 输出 e

# math.log(x[, base]): 返回 x 的自然对数(底为 e)或指定底数的对数。
print(math.log(8, 2))  # 输出 3.0

# math.pow(x, y): 返回 x^y。
print(math.pow(2, 3))  # 输出 8.0

# math.sqrt(x): 返回 x 的平方根。
print(math.sqrt(16))  # 输出 4.0

# math.sin(x): 返回 x(弧度)的正弦。
print(math.sin(math.pi / 2))  # 输出 1.0

# math.cos(x): 返回 x(弧度)的余弦。
print(math.cos(0))  # 输出 1.0


# math.pi: 圆周率 π。
print(math.pi)  # 输出 3.141592653589793

# math.e: 自然数 e。
print(math.e)  # 输出 2.718281828459045
5
4
5.0
2.718281828459045
3.0
8.0
4.0
1.0
1.0
3.141592653589793
2.718281828459045

8.2.2 random模块

用于生成随机数、抽样与打乱。设置种子以便结果可复现。

import random

random.seed(42)  # 固定种子,结果可复现
print(random.random())        # 0~1 间随机浮点数
print(random.randint(1, 6))   # 含端点的随机整数
print(random.choice(['A','B','C']))
print(random.sample(range(10), 3))  # 不放回抽样

data = [1,2,3,4,5]
random.shuffle(data)          # 就地打乱
print(data)
0.6394267984578837
1
C
[4, 3, 8]
[5, 4, 3, 1, 2]

8.2.3 statistics模块

用于基础统计量。注意:stdev/variance 为“样本”统计量(n-1),pstdev/pvariance 为“总体”统计量(n)。

import statistics as stats

xs = [1, 2, 2, 3, 4]
print(stats.mean(xs))             # 均值
print(stats.median(xs))           # 中位数
print(round(stats.stdev(xs), 3))  # 样本标准差
print(round(stats.pstdev(xs), 3)) # 总体标准差
2.4
2
1.14
1.02

8.2.4 os模块

和操作系统交互,处理文件等等

import os
# 获得当前目录
print("当前工作目录是:", os.getcwd())

# 获得当前目录下的文件列表
print("当前目录下的文件列表:", os.listdir())

# 测试一个路径(可以是文件或者文件夹)是否存在
print(os.path.exists('某个文件.txt'))

8.2.5 json模块

JSON格式非常类似于字典。如果要把 Python 中的字典、列表数据对象保存到本地文件,可以先转为 JSON 格式再保存;读取时则反过来。

数据(字典、列表等) -> JSON 格式 -> 保存到本地文件(*.json)

从本地文件(*.json)中读取JSON对象 -> 数据(字典、列表等)

import json

# 对象 <-> JSON 字符串(便于本地文件/网络传输等场景)

# Dict/List -> JSON
obj = {"name": "Alice", "scores": [95, 88], "passed": True}
s = json.dumps(obj, ensure_ascii=False, indent=2)  # 转为 JSON 字符串
print(s)

# JSON -> Dict/List
obj2 = json.loads(s)  # 还原为 Python 对象(字典)
print(obj2["name"], obj2["scores"][0])
{
  "name": "Alice",
  "scores": [
    95,
    88
  ],
  "passed": true
}
Alice 95

演示:将字典(或列表)写入本地 JSON 文件 → 读取 → 修改 → 重新写到本地文件:

import json, os
from pathlib import Path

path = Path("example_data.json")

# 初始写入:dict -> 本地 JSON 文件
data = {"name": "Alice", "score": 95, "passed": True}
with path.open("w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# 读取:本地 JSON 文件 -> dict
with path.open("r", encoding="utf-8") as f:
    obj = json.load(f)
print("读取:", obj)

# 修改并写回(覆盖写入)
obj["score"] += 3
obj["tags"] = ["midterm", "bonus"]
with path.open("w", encoding="utf-8") as f:
    json.dump(obj, f, ensure_ascii=False, indent=2)

# 再次读取验证
print("修改后:", json.loads(path.read_text(encoding="utf-8")))
读取: {'name': 'Alice', 'score': 95, 'passed': True}
修改后: {'name': 'Alice', 'score': 98, 'passed': True, 'tags': ['midterm', 'bonus']}

8.2.6 日期与时间:datetime / time / calendar

用来处理日期和时间

# datetime模块的一些常见用法

import datetime

# datetime.datetime.now(): 获取当前日期和时间。
now = datetime.datetime.now()
print(f"当前的日期和时间是: {now}")  # 输出当前日期和时间

# datetime.date.today(): 获取当前日期。
today = datetime.date.today()
print(f"当前的日期是: {today}")  # 输出当前日期

# datetime.timedelta(): 表示时间间隔。
# 创建一个时间间隔对象,表示1天和5分钟。
delta = datetime.timedelta(days=1, minutes=5)

# 时间加减
future = now + delta  # 当前时间加上时间间隔
print(f"1天05分钟后的时间是: {future}")  # 输出未来的日期和时间

past = now - delta  # 当前时间减去时间间隔
print(f"1天05分钟之前的时间是: {past}")  # 输出过去的日期和时间

# datetime.datetime.strftime(): 格式化日期和时间。
formatted_date = now.strftime("%Y-%m-%d %H:%M:%S")
print(f"时间格式化(注意会返回一个字符串): {formatted_date}")  # 输出格式化后的日期和时间

# datetime.datetime.strptime(): 从字符串解析日期和时间。
parsed_date = datetime.datetime.strptime("2022-01-01 12:34:56", "%Y-%m-%d %H:%M:%S")
print(f"从字符串解析出日期和时间: {parsed_date}")  # 输出从字符串解析出的日期和时间
当前的日期和时间是: 2025-10-11 14:51:48.159113
当前的日期是: 2025-10-11
1天05分钟后的时间是: 2025-10-12 14:56:48.159113
1天05分钟之前的时间是: 2025-10-10 14:46:48.159113
时间格式化(注意会返回一个字符串): 2025-10-11 14:51:48
从字符串解析出日期和时间: 2022-01-01 12:34:56

注意:上述例子也表明了,datetime的对象和一个包含了日期的字符串是不同的。类比就是数字1和字符串"1"的区别。

  1. datetime(日期时间)可以获得表达的日期,时间等等,可以和delta(时间间隔)相互加减
  2. 日期字符串只是一个字符串,则没有这个功能。
  3. 如果要对日期字符串进行时间运算,需要解析成datetime,再运算,这个过程就是上述范例从下往上的应用。

此外,常用的 timecalendar 模块也很有用:

import time, calendar

# 计时:用于简单性能测量
t0 = time.perf_counter()
time.sleep(0.1)  # 休眠 0.1 秒
elapsed = time.perf_counter() - t0
print(f"耗时约: {elapsed:.3f} 秒")

# 时间格式化(time 模块)
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(0)))  # 本地时区的 1970-01-01

# 月历与每月天数(calendar 模块)
print(calendar.month(2024, 1))
print(calendar.monthrange(2024, 2))  # (该月第一天是星期几, 当月天数)
耗时约: 0.105 秒
1970-01-01 08:00:00
    January 2024
Mo Tu We Th Fr Sa Su
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

(calendar.THURSDAY, 29)

更多范例:

from datetime import date, timedelta
import datetime, calendar

# 1) 计算两个日期相差多少天
d1 = date(2024, 1, 1)
d2 = date(2024, 3, 15)
print(f"从 {d1}{d2} 相差 {(d2 - d1).days} 天")

# 2) 今天的 N 天之后是什么日期
N = 30
today = date.today()
print(f"今天是 {today}{N} 天后是 {today + timedelta(days=N)}")

# 3) 某年每个月的第三个星期四(使用 calendar.monthcalendar)
year = 2024
third_thursdays = []
for month in range(1, 13):
    cal = calendar.monthcalendar(year, month)
    th = calendar.THURSDAY  # 星期四的索引
    # 若第一周就有星期四,则第三个星期四在 cal[2][th];否则在 cal[3][th]
    day = cal[2][th] if cal[0][th] else cal[3][th]
    third_thursdays.append(datetime.date(year, month, day))

print("第三个星期四:", [d.strftime("%Y-%m-%d") for d in third_thursdays])
从 2024-01-01 到 2024-03-15 相差 74 天
今天是 2025-10-11 ,30 天后是 2025-11-10
第三个星期四: ['2024-01-18', '2024-02-15', '2024-03-21', '2024-04-18', '2024-05-16', '2024-06-20', '2024-07-18', '2024-08-15', '2024-09-19', '2024-10-17', '2024-11-21', '2024-12-19']

8.3 建立你自己的模块

要完成一个特定的项目,你可能要写不计其数代码,没有理由把这么多代码都放在一个.py文件下。

一般可以把相关的代码放在一个.py文件中,然后在其他文件里用模块的方式调用。

例如,我们前面写过一个add函数,可以把2个数相加。

def add(x,y):
    return x + y

print(add(1,2))
3

也写过一个翻倍函数

def do_double(x):
    return x * 2

print(do_double(2))
4

这2个函数,显然都可以归类为计算函数,我们把这2个函数放到一个专门的文件中,例如my_calc.py

你还可以保存变量,例如my_pi = 3.14

新建一个文件my_calc.py,把以下内容放进去,存盘。

注意: 为了方便,这里要求把my_calc.py文件放在你现在正在工作的.py的同一个目录下。

  1. 打开vscode的资源管理器(左侧最顶上的图标),找到你当前的文件,在同一个目录下,右键点击空白(或者文件夹的名字),“新建文件”,然后命名为 my_calc.py即可。
  2. 如果你打开vscode的资源管理器后,看到一片空白,出现“打开文件夹”的按钮,即你没有打开工作文件夹,先去打开。这是我们开头几节课强调过的内容。

否则:很可能会出现 ModuleNotFoundError: No module named 'my_calc' 的错误

def add(x,y):
    return x + y

def do_double(x):
    return x * 2

my_pi = 3.14

在我们后续的任务中,如果要调用这2个函数,或者你自己定义的变量my_pi,就可以用 import 导入。

import my_calc

print(my_calc.add(3,2))
print(my_calc.my_pi)

当然,如简称、直接导入名称等等,和前述一样。

以后,你就可以按照你自己逻辑,如同类的函数、数据,完全特定任务需要的组件等等,来组织自己的模块。

按逻辑把你的代码分类组织,是一个好习惯。

import my_calc as mc
from my_calc import add, my_pi
print(add(3, 2), my_pi)

小提示: - 避免将你的文件命名为与标准库同名(如math.pydatetime.py),否则导入时会优先导入你的文件,导致冲突。 - 若需要在模块内放少量自测代码,可使用:

def add(x, y):
    return x + y

if __name__ == "__main__":
    # 仅当直接运行本文件时执行,不会在被导入时执行
    print(add(1, 2))

8.3.1 导入路径与排错

Python 导入模块的搜索路径由当前工作目录和 sys.path 决定。可用下述方法查看:

import sys
print(sys.path)

常见 ModuleNotFoundError 排查: - 确认 .py 文件是否在当前工作目录或已被加入到 sys.path 的路径中。 - 检查文件/文件夹命名是否与标准库或已安装第三方包冲突。 - 确认是否在正确的环境中运行(如虚拟环境/conda 环境)。

8.4 包package

包(package)是按目录组织的一组模块。一个包通常是一个包含多个 .py 文件的文件夹:

my_pkg/
  __init__.py
  calc.py
  utils.py

calc.py 示例:

def add(x, y):
    return x + y

使用方式:

from my_pkg.calc import add
print(add(1, 2))

说明: - __init__.py 用于标识“普通包”,有助于明确包的边界,并可在其中暴露常用接口。 - Python 3.3+ 也支持“命名空间包”,可不含 __init__.py,多个目录可被视为同一个包的不同部分;初学阶段使用普通包更直观。