8  模块

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

写程序时,我们通常会复用已有代码,而不是从头写所有功能。这些已有代码一般也是用模块的形式组织。包括我们后面主要用到的数据分析三件套。

常见模块大致可以分为三类:

  1. 标准库模块:Python 自带,例如 mathrandomdatetime。通常不需要额外安装。
  2. 第三方模块:由其他开发者提供,例如 numpypandasmatplotlib。本课程使用 Anaconda,许多常用第三方模块已经安装好。
  3. 自己写的模块:你自己创建的 .py 文件,例如后面会用到的 my_calc.py
Note

本章内容较多,可以按两个层次学习。

必修主线:导入模块、使用常用模块、编写自己的模块

  • 模块的基本概念:.py 文件就是模块
  • 标准库模块、第三方模块、自己写的模块
  • import 模块名
  • import 模块名 as 别名
  • 通过 模块名.函数名 使用模块内容
  • from 模块名 import 对象名
  • 推荐导入方式,以及避免 from 模块名 import *
  • 如何查询模块帮助:help()
  • 常用标准库基础:math
  • 常用标准库基础:random
  • 常用标准库基础:statistics
  • JSON 的用途:保存数据、交换数据
  • json.dump() / json.load():读写本地 JSON 文件
  • encoding="utf-8"ensure_ascii=False
  • 当前工作目录与文件路径提醒
  • 日期和时间基础:datetime.datetime.now()datetime.date.today()datetime.timedelta
  • 建立自己的模块:创建 my_calc.py
  • 导入自己写的模块
  • ModuleNotFoundError 的基本排查
  • 文件命名不要和标准库/第三方库重名

进阶学习:字符串转换、导入路径、包和更多标准库用法

  • json.dumps() / json.loads():JSON 字符串与 Python 对象互转
  • indent=2
  • 覆盖写入、路径管理、Path
  • os 模块
  • time 模块
  • calendar 模块
  • 日期字符串解析和格式化:strftime()strptime()
  • 更多日期计算例子
  • 修改模块后重启内核/重新运行脚本
  • if __name__ == "__main__"
  • 当前工作目录、sys.path 与导入路径
  • 包 package 的基本结构
  • __init__.py
  • 命名空间包

8.1 引入模块

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

例如用于数学运算的math,这是Python自带的模块,其中有大量数学函数,例如开平方根的sqrt

8.1.1 import 语句

引入模块也很简单

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

注意:在同一次 Python 运行过程、同一个脚本或 Notebook 中,import 一次后,后续代码就可以使用。因此,一般会把 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。如果代码中频繁使用这个函数,可以直接导入 sqrt,后面就不需要每次都写 math.sqrt()

同样,在同一次 Python 运行过程、同一个脚本或 Notebook 中,导入一次后,后续代码就可以使用。

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

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

from <模块名> import *

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

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

一般建议:

  1. 优先使用 import 模块名,例如 import math
  2. 对约定俗成的第三方库使用常见别名,例如 import numpy as np
  3. 只有在少量对象使用非常频繁、且名称含义明确时,才使用 from 模块名 import 对象名
  4. 避免使用 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 文件 → 读取 → 修改 → 重新写到本地文件。

注意:下面的代码会创建或覆盖当前工作目录下的 example_data.json,因此这里不自动执行。

import json
from pathlib import Path

path = Path("example_data.json")

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

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

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

# 再次读取验证
with open(path, "r", encoding="utf-8") as f:
    obj = json.load(f)
print("读取:", obj)

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}")  # 输出从字符串解析出的日期和时间
当前的日期和时间是: 2026-05-17 21:48:55.032684
当前的日期是: 2026-05-17
1天05分钟后的时间是: 2026-05-18 21:53:55.032684
1天05分钟之前的时间是: 2026-05-16 21:43:55.032684
时间格式化(注意会返回一个字符串): 2026-05-17 21:48:55
从字符串解析出日期和时间: 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.104 秒
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 天
今天是 2026-05-17 ,30 天后是 2026-06-16
第三个星期四: ['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个函数都可以归类为计算函数。我们可以把它们放到一个专门的文件中,例如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),否则导入时会优先导入你的文件,导致冲突。 - 如果已经在同一个 Python 会话中导入过 my_calc,再修改 my_calc.py 后,重新 import my_calc 不一定会加载新内容。初学阶段最简单的做法是重启内核,或者重新运行脚本。 - 若需要在模块内放少量自测代码,可使用:

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

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

8.3.1 导入路径与排错

Python 导入模块时,会按照一定的路径顺序查找。这个查找范围和当前工作目录、sys.path 有关。注意,“当前工作目录”不一定等于“正在编辑的 .py 文件所在目录”。可用下述方法查看:

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,多个目录可被视为同一个包的不同部分;初学阶段使用普通包更直观。