今天你 AI 赋能了吗?打开浏览器,在地址栏输入 xuefulu.ai ,按下 Enter 即可访问学府路1号!
2026年10月14日 星期三 10:40:29
常用网址: | | 学府路1号 https://xuefulu.ai | 下载 Python https://xuefulu.ai/Python.html | 下载 TRAE https://xuefulu.ai/Trae.html

一、标准库:装好就有的一整套工具

把 Python 装好,除了语言本身,还附送了几百个标准库(standard library)。它们的最大好处是:不用装、不用 pip、直接 import 就能用。

第 11 篇《导入库的方法》已经介绍过「标准库 vs 第三方库」的区别。本篇把最常用的一批挑出来,讲清楚它们各自解决什么问题:

模块管什么一句话记住
os文件、文件夹、路径、环境变量和老式操作系统打交道
pathlib路径(更现代的写法)os.path 的升级版
json读写 JSON 格式的数据数据交换的通用格式
csv读写逗号分隔的表格文件Excel 能直接打开的纯文本表
datetime日期与时间算日期、格式化时间
statistics平均数、中位数、标准差统计指标一句话算完
random随机数、抽样、打乱抽几个来检查、造模拟数据
time时间戳、计时、让程序等等给数据盖时间、量代码快不快
collections会数数的字典、自动分类的字典数谁最多、按类别累加

为什么是这几个?因为它们正好凑齐了一条「数据活儿」的完整链路:把数据存下来 → 读回来 → 算清楚 → 看得见。第 26 篇《文件处理》讲的是纯文本怎么读写,本篇讲的是「数据用什么格式存、用什么工具算」。《12-time库与random库》里讲过这两个库的基础用法,本篇补的是做数据分析时才会用到的那几个用法。

这一篇的量偏大,建议分两次读:第一遍读 pathlib + json + csv(最常用),把 datetime、statistics、random、time 和第十节那几个第三方库,留到真正要做数据分析时再回来查。

课程思政|科技自立自强 · 现成的轮子,也得看懂

Python 装好,就自带了几百个标准库,import 一下就能用,这是无数人替我们攒下的家底。但用得顺手,不代表真懂了:会用别人造好的轮子,和知道车为什么能跑,是两回事。os、json、csv 背后无非是「读写文件、整理数据」这些硬功夫,把原理弄明白,将来自带库里没有的需求,你才有底气自己动手。

二、pathlib:拼路径、拆路径

先说 pathlib,因为后面几个库都要处理「文件放在哪儿」。它是现在推荐的写法:把路径当成一个对象来看待。

2.1 造一个路径

Python
from pathlib import Path

p = Path('/Users/x/data/scores.csv')
print('完整路径:', p)
print('文件名:', p.name)
print('去掉后缀:', p.stem)
print('后缀:', p.suffix)
print('所在文件夹:', p.parent)

输出:

输出
完整路径: /Users/x/data/scores.csv
文件名: scores.csv
去掉后缀: scores
后缀: .csv
所在文件夹: /Users/x/data

注意这几个属性不用记拼写规则,看一眼就知道是什么意思——这正是 pathlib 比老写法好用的地方。

2.2 拼路径:用 / 就行

Python
from pathlib import Path

folder = Path('data')
print(folder / 'scores.csv')
print(folder / '2026' / '10月' / 'scores.csv')

输出:

输出
data/scores.csv
data/2026/10月/scores.csv

写 folder / '名字' 就够了,不用自己判断该用斜杠还是反斜杠——这一点很重要,因为 Windows 和 macOS 的路径分隔符不一样,手写字符串很容易在换电脑后失效。

写法拼出来的路径换到 Windows 上
Path('data') / 'a.csv'data/a.csv自动变成 data\a.csv
'data' + '/' + 'a.csv'data/a.csv还是斜杠,可能失效

2.3 常用的四个动作

Python
import pathlib, tempfile
from pathlib import Path

work = Path(tempfile.mkdtemp())
f = work / 'score.txt'

print('存在吗(写之前):', f.exists())
f.write_text('小满 92', encoding='utf-8')     # 一行写文件
print('存在吗(写之后):', f.exists())
print('内容:', f.read_text(encoding='utf-8'))  # 一行读文件
f.unlink()                                     # 删除
print('删除后还在吗:', f.exists())

输出:

输出
存在吗(写之前): False
存在吗(写之后): True
内容: 小满 92
删除后还在吗: False

这几个方法都是「一个动作一行代码」,不用再写 open() 加 with 那一套:

方法作用对应老写法
p.exists()判断存在吗os.path.exists(p)
p.read_text()一次读出全部文字open(p).read()
p.write_text(s)一次写入文字(覆盖原内容)open(p, 'w').write(s)
p.unlink()删除这个文件os.remove(p)
p.mkdir(parents=True, exist_ok=True)建文件夹os.makedirs(p)

2.4 老写法 os.path 也认一下

老教程和老代码里全是 os.path,看得懂就行,新代码建议用 pathlib。

Python
import os

print('拼路径:', os.path.join('data', 'scores.csv'))
print('取文件名:', os.path.basename('/Users/x/data/scores.csv'))
print('拆后缀:', os.path.splitext('scores.csv'))
print('存在吗:', os.path.exists('这个文件不存在.txt'))

输出:

Python
拼路径: data/scores.csv
取文件名: scores.csv
拆后缀: ('scores', '.csv')
存在吗: False
os.path.join() 拼出来的分隔符跟你的系统走:macOS / Linux 上是 /,Windows 上是 \。所以页面上这里显示 data/scores.csv,你在 Windows 上跑会看到 data\scores.csv——这是对的,不是错。要跨系统稳定,就用 pathlib 的 /。

三、json:数据交换的通用格式

要把数据存进文件,得先定一个格式。自己发明格式的问题很明显:今天用逗号分,明天想加一列就崩了。

JSON 是一个大家都认的格式:它长得像 Python 的字典和列表,几乎所有编程语言都能读写。它的规则就四条:

JSON 里的东西写法对应 Python
对象{"name": "小满"}字典 dict
数组[1, 2, 3]列表 list
字符串"abc"(必须双引号)字符串 str
数字 / 真值 / 空值12 3.5 true false null小写是 True / False / None

3.1 四个函数,两两配对

函数作用记法
json.dumps(对象)Python 对象 → JSON 字符串dump string:转成字符串
json.loads(字符串)JSON 字符串 → Python 对象load string:从字符串读
json.dump(对象, 文件)Python 对象 → 写进文件直接落盘
json.load(文件)从文件 → Python 对象从文件读
Python
import json

data = {'name': '小满', 'score': 92, 'courses': ['数学', '英语']}

text = json.dumps(data, ensure_ascii=False)
print(text)

back = json.loads(text)
print(back)
print('读回来还是字典吗:', type(back).__name__)
print('取出成绩:', back['score'])

输出:

输出
{"name": "小满", "score": 92, "courses": ["数学", "英语"]}
{'name': '小满', 'score': 92, 'courses': ['数学', '英语']}
读回来还是字典吗: dict
取出成绩: 92

3.2 ensure_ascii=False 是关键

上面第二个参数如果省掉,中文会变成一堆 \u… 编码,人能看但很难读:

Python
import json

data = {'name': '小满'}
print('默认:', json.dumps(data))
print('加参数:', json.dumps(data, ensure_ascii=False))

输出:

输出
默认: {"name": "\u5c0f\u6ee1"}
加参数: {"name": "小满"}

两种都能被 loads() 正确读回来,功能没问题。但存给学生看的文件、或者要拿给人检查的文件,一定加上 ensure_ascii=False。

3.3 存在文件里

Python
import json, pathlib, tempfile

work = pathlib.Path(tempfile.mkdtemp())
f = work / 'scores.json'
data = {'班级': '统计一班', '成绩': [92, 78, 85]}

# 存:dump(obj, 文件) —— 注意别写成 dumps 再自己写字符串
f.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
print(f.read_text(encoding='utf-8'))

# 读:load(文件)
back = json.loads(f.read_text(encoding='utf-8'))
print('读回来的成绩:', back['成绩'])

输出:

Python
{
  "班级": "统计一班",
  "成绩": [
    92,
    78,
    85
  ]
}
读回来的成绩: [92, 78, 85]
indent=2 让 JSON 带缩进换行,人看着舒服、改起来方便;机器读不需要缩进。给人和给机器,选一个就行——数据量大时缩进会让文件明显变大。

3.4 不是所有东西都能转成 JSON

Python
import json
print(json.dumps({1, 2, 3}))   # 集合

运行后会报错(逐字照抄解释器):

报错
TypeError: Object of type set is not JSON serializable

能转的只有这几种:字典、列表、字符串、整数、小数、布尔、None。像集合、函数、自定义对象都转不了。元组会被转成列表(读回来时是列表,不再是元组)——这一点写程序时要留意。

课程思政|工匠精神与严谨求实 · 少一个引号,别人就读不了

JSON 只认双引号、不允许多余的逗号,规矩看着苛刻,可正是这份严格,换来了它在几乎所有语言之间都能通用。写数据也是这个道理:把一份结果存成 JSON 交给别人,格式上每一处将就,最后都得别人替你还。养成存完就用 json.loads() 试读一遍的习惯,比事后回头翻找省事得多。

四、表格格式:csv、tsv 与 xlsx

如果数据本来就是「一张表」,每行一条记录、每列一个字段,那 CSV(逗号分隔值)最合适。它的好处是:纯文本、体积小、Excel 双击就能打开、几乎所有数据分析工具都认。

一个 CSV 文件用记事本打开,长这样(三个字段、两条记录):

姓名分数班级
小满92统计一班
阿禾78统计一班

它在磁盘上的原始文字其实是这样一行行的,字段之间真的就是一个英文逗号:

姓名,分数,班级小满,92,统计一班阿禾,78,统计一班

4.1 读:csv.reader() 用下标取

Python
import csv, io

text = '姓名,分数\n小满,92\n阿禾,78\n'
for row in csv.reader(io.StringIO(text)):
    print(row)

输出:

输出
['姓名', '分数']
['小满', '92']
['阿禾', '78']

每一行是一个列表,按位置取(row[0] 是姓名、row[1] 是分数)。列一多,这种写法就容易数错。

4.2 读:csv.DictReader() 用字段名取

Python
import csv, io

text = '姓名,分数\n小满,92\n阿禾,78\n'
for row in csv.DictReader(io.StringIO(text)):
    print(row)
    print('这位同学是:', row['姓名'])

输出:

输出
{'姓名': '小满', '分数': '92'}
这位同学是: 小满
{'姓名': '阿禾', '分数': '78'}
这位同学是: 阿禾

每一行是一个字典,用第一行当字段名。推荐用这种:代码可读性好,以后列顺序变了也不受影响。

4.3 写:csv.writer()

Python
import csv, io

buf = io.StringIO()
w = csv.writer(buf)
w.writerow(['姓名', '分数'])
w.writerow(['小满', 92])
w.writerow(['阿禾', 78])
print('磁盘上真正的文字:')
print(buf.getvalue())

输出:

输出
磁盘上真正的文字:
姓名,分数
小满,92
阿禾,78
用 open() 配合 csv 读写文件时,必须写 newline='':
open('a.csv', 'w', encoding='utf-8', newline='')
漏掉它,Windows 上每写一行会多出一个空行(因为 csv 自己写的换行和 open 的换行叠加了)。这是 csv 最经典的坑。

4.4 真正读写文件的标准写法

Python
import csv, pathlib, tempfile

work = pathlib.Path(tempfile.mkdtemp())
f = work / 'scores.csv'
rows = [['姓名', '分数'], ['小满', 92], ['阿禾', 78]]

# 写:newline='' 一定不能少
with open(f, 'w', encoding='utf-8', newline='') as fp:
    csv.writer(fp).writerows(rows)

# 读:用 DictReader,按字段名取值
with open(f, 'r', encoding='utf-8', newline='') as fp:
    for row in csv.DictReader(fp):
        print(row['姓名'], '的分数是', row['分数'], '(类型:' + type(row['分数']).__name__ + ')')

输出:

输出
小满 的分数是 92 (类型:str)
阿禾 的分数是 78 (类型:str)

注意最后那个类型:读进来的分数是字符串 '92',不是数字 92。要参与计算必须先转:int(row['分数'])。这一点和 input() 收到的一律是文字是同一个道理。

存给 Excel 用的 CSV,编码建议写 utf-8-sig(带 BOM):encoding='utf-8-sig'。这样 Excel 打开中文不会乱码。读的时候用 utf-8 或 utf-8-sig 都能读。

4.5 csv 之外,Excel 还能打开哪些格式

别再以为「Excel 文件」只有一种。同样是 Excel 能双击打开的表格,后缀不同,里面的东西差得很远:

后缀里面到底是什么标准库能读写吗什么时候用
.csv纯文本,字段之间用逗号隔开能,用 csv数据交换、导给别的软件——最通用
.txt / .tsv纯文本,字段之间用制表符隔开能,还是用 csv(换个分隔符)内容里本来就带逗号,或者习惯按 Tab 分列
.xlsx不是文本:一个 zip 压缩包,里面装着一堆 XML不能,要装第三方库正式报表,要保留格式 / 公式 / 多个工作表
.xls老版本 Excel 的二进制格式不能,要装第三方库只有老系统还在用,新表一律别用它

一句话记住:能用记事本打开、看得懂的是纯文本(csv / tsv),标准库全包;用记事本打开全是乱码的是二进制(xlsx / xls),必须请第三方库出场。

想读 .xlsx?要装 openpyxl 或 pandas,写法在第十节 10.3。这一节先把「纯文本表格」练熟——它是所有软件都认的「通用货币」。

4.6 分隔符不一定是逗号

CSV 的 C 是 comma(逗号),但只要换个分隔符,同一个 csv 模块也能写出「制表符分隔」的 .tsv——Excel 照样双击能开:

Python
import csv, io

buf = io.StringIO()
w = csv.writer(buf, dialect='excel-tab')     # 用制表符代替逗号
w.writerow(['姓名', '分数'])
w.writerow(['小满', 92])
w.writerow(['阿禾', 78])
print(repr(buf.getvalue()))

输出:

输出
'姓名\t分数\r\n小满\t92\r\n阿禾\t78\r\n'

看仔细:字段之间变成了 \t(制表符,就是键盘上那个 Tab),每行结尾是 \r\n。这两个都是「看不见的字符」,所以在 Python 里要用 repr() 打印,才能把它们看清楚。

拿到一份别人给的表,不知道是逗号分隔还是制表符分隔怎么办?让 csv 自己看:

Python
import csv

texts = [
    '姓名,分数\n小满,92\n阿禾,78\n小鹿,85\n',
    '姓名\t分数\n小满\t92\n阿禾\t78\n小鹿\t85\n',
]
for t in texts:
    dialect = csv.Sniffer().sniff(t)
    print('这一份用的分隔符是:', repr(dialect.delimiter))

输出:

输出
这一份用的分隔符是: ','
这一份用的分隔符是: '\t'

csv.Sniffer() 就是「探测器」:给它一段内容,它替你猜分隔符。数据来源杂的时候很好用,比自己写 if ',' in line 靠谱。

五、datetime:日期与时间

给数据加时间戳、算「距今多少天」、把时间显示成指定格式,都要用 datetime。它里面最常用的四个东西:

名字是什么例子
datetime.datetime日期 + 时间2026-10-04 19:30:00
datetime.date只有日期2026-10-04
datetime.timedelta一段时间(几天几小时)timedelta(days=10)
datetime.now()取当前时间(每次都不一样)—

5.1 造一个时间、显示成你要的样子

为了输出可重复,下面用一个固定的时间点当例子;真实代码里写 datetime.now() 就行。

Python
from datetime import datetime

d = datetime(2026, 10, 4, 19, 30, 0)
print('原样:', d)
print('自定义格式:', d.strftime('%Y年%m月%d日 %H:%M'))
print('只要日期:', d.strftime('%Y-%m-%d'))
print('星期几:', d.strftime('%A'))

输出:

输出
原样: 2026-10-04 19:30:00
自定义格式: 2026年10月04日 19:30
只要日期: 2026-10-04
星期几: Sunday

strftime 里的格式符号是最需要记的一小块:

符号含义例子输出
%Y四位年份2026
%m两位月份10
%d两位日期04
%H / %M / %S时 / 分 / 秒(24 小时制)19 30 00
%A星期几(英文)Sunday

5.2 把字符串变成时间:strptime

Python
from datetime import datetime

d = datetime.strptime('2026-10-04', '%Y-%m-%d')
print(d)
print('类型:', type(d).__name__)
print('年份:', d.year)

输出:

输出
2026-10-04 00:00:00
类型: datetime
年份: 2026

strftime 和 strptime 只差一个字母,方向刚好相反,是新手最容易记混的一对:

函数方向记法
strftime时间对象 → 字符串format:去格式化
strptime字符串 → 时间对象parse:去解析

5.3 算日期:timedelta

Python
from datetime import date, timedelta

d = date(2026, 10, 4)
print('十天后:', d + timedelta(days=10))
print('一周前:', d - timedelta(weeks=1))
print('相差几天:', (date(2026, 10, 4) - date(2026, 10, 1)).days)

输出:

输出
十天后: 2026-10-14
一周前: 2026-09-27
相差几天: 3

两个日期相减得到 timedelta,取 .days 就是天数;日期加减 timedelta 得到新日期。月份、闰年、跨年这些麻烦事它都替你算好了,千万不要自己用 30 天当一个月。

AI 助手小贴士|标准库几十个模块,先让 AI 帮你挑最常用的三个上手。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:从 os、datetime、json、random、math、time 里挑 3 个初学者最常用的,各给一个能直接跑的最小例子

六、statistics:描述性统计一次算全

statistics 是标准库自带的「统计小工具」,专门算平均数、中位数、众数、标准差这些指标。做数据分析,第一步就是把数字算对——这一节把描述性统计常用的指标过一遍,能算的都算给你看。

6.1 四个最常用的指标

Python
import statistics

scores = [88, 92, 79, 95, 61]

print('平均分:', statistics.mean(scores))
print('中位数:', statistics.median(scores))
print('标准差:', round(statistics.stdev(scores), 2))
print('最高分:', max(scores))

输出:

输出
平均分: 83
中位数: 88
标准差: 13.69
最高分: 95

四个词的区别:mean 是全部加起来再平均;median 是从小到大排在正中间的那个;stdev 是标准差,衡量分数「散不散」,越小越整齐;mode 是出现次数最多的那个。

函数算什么什么时候用
statistics.mean(x)平均值大家都差不多时,最直观
statistics.median(x)中位数数据里有极端值(有人考 0 分、有人收入特别高)
statistics.mode(x)众数分类数据:哪种最受欢迎
statistics.stdev(x)标准差比较两队稳不稳定

6.2 描述性统计,看四类指标

「描述性统计」听着唬人,其实就是用几个数字把一堆数据的样子说清楚。这些数字分四类,各回答一个问题:

类别回答什么问题代表指标
集中趋势数据的「平均水平」在哪平均值、中位数、众数
离散程度数据散不散、稳不稳定极差、方差、标准差、变异系数
分布位置数据从哪到哪、怎么分布最小值、最大值、四分位数、四分位距
变量间关系两组数据有没有一起变相关系数、回归直线

下面按这个顺序一个一个试。所有例子用的都是同一组十位同学的分数,方便前后对照。

6.3 集中趋势:平均数不止一种

Python
import statistics

scores = [88, 92, 79, 95, 61, 84, 90, 73, 100, 0]

print('平均值:', statistics.mean(scores))
print('中位数:', statistics.median(scores))
print('下中位数:', statistics.median_low(scores))
print('上中位数:', statistics.median_high(scores))

输出:

输出
平均值: 76.2
中位数: 86.0
下中位数: 84
上中位数: 88

十个数是偶数个,「正中间」落在第 5 个(84)和第 6 个(88)之间:median 取两者的平均 86.0,median_low 取靠下的 84,median_high 取靠上的 88。要求「结果本身就在数据里」(比如「中位数同学考了多少分」),就用 low / high。

还有两种平均数专门对付特殊场景。看增长率用几何平均;看速度、单价这种「每段总量相同」的用调和平均:

Python
import statistics

# 连续三年的增长率:1.10 表示比上一年涨 10%
growth = [1.10, 0.95, 1.20]
print('几何平均(平均增长率):', round(statistics.geometric_mean(growth), 4))

# 来回两趟路程一样,去时 60、回时 40,平均速度是多少?
print('调和平均(平均速度):', statistics.harmonic_mean([60, 40]))

输出:

输出
几何平均(平均增长率): 1.0784
调和平均(平均速度): 48.0

平均速度是 48 不是 50——很多人第一次见到会愣一下:慢的那一趟花的时间更久,所以它拖得更狠。不能把 60 和 40 加起来除以 2。

最常见的一种是加权平均:期末总评 = 平时分 30% + 期末考 70%,权重不一样,就不能简单平均。标准库用 fmean() 的 weights 参数:

Python
import statistics

# 总评 = 平时分 30% + 期末考 70%
usual = [85, 90, 78]
final = [80, 95, 70]

for i, (u, f) in enumerate(zip(usual, final), 1):
    total = round(statistics.fmean([u, f], weights=[0.3, 0.7]), 1)
    print(f'第 {i} 位同学总评:', total)

输出:

输出
第 1 位同学总评: 81.5
第 2 位同学总评: 93.5
第 3 位同学总评: 72.4

拿第 1 位核对:85 × 0.3 + 80 × 0.7 = 81.5。权重是「乘完再除以权重之和」,所以 [0.3, 0.7] 和 [3, 7] 算出来完全一样,写小数还是写整数都不影响结果。

最后是众数——看谁出现次数最多。分类数据(奶茶口味、专业、血型)没有「平均」,就靠它:

Python
import statistics

votes = ['珍珠奶茶', '珍珠奶茶', '柠檬茶', '柠檬茶', '椰子水']

print('众数:', statistics.mode(votes))
print('并列最多的所有选项:', statistics.multimode(votes))
print('一共有几种选择:', len(set(votes)))

输出:

输出
众数: 珍珠奶茶
并列最多的所有选项: ['珍珠奶茶', '柠檬茶']
一共有几种选择: 3

注意 mode() 只给一个(并列时给最先进来的那个),multimode() 会把并列的都给出来。想说「哪几种口味都受欢迎」,用 multimode() 才诚实。

6.4 平均数会骗人,中位数不会

Python
import statistics

# 全班 9 人都考得不错,只有一位同学交了白卷
scores = [88, 92, 79, 95, 61, 84, 90, 73, 100, 0]

print('平均分:', round(statistics.mean(scores), 1))
print('中位数:', statistics.median(scores))

输出:

输出
平均分: 76.2
中位数: 86.0

平均分 76.2、中位数 86.0,差了将近 10 分。原因就是那个 0 分——它把平均值往下拽,却几乎动不了中位数。看到「平均」先别急着信,问一句「有没有极端值」,再把中位数一起算出来。

6.5 离散程度:数据散不散

两个班平均分都是 80:一个班人人 75~85,另一个班一半 100 一半 60——「水平」一样,「整齐程度」差远了。衡量「散不散」的指标有好几个:

Python
import statistics

scores = [88, 92, 79, 95, 61, 84, 90, 73, 100, 0]

print('极差(最大减最小):', max(scores) - min(scores))
print('样本方差:', round(statistics.variance(scores), 2))
print('总体方差:', round(statistics.pvariance(scores), 2))
print('样本标准差:', round(statistics.stdev(scores), 2))
print('总体标准差:', round(statistics.pstdev(scores), 2))
print('变异系数:', round(statistics.stdev(scores) / statistics.mean(scores), 4))

输出:

输出
极差(最大减最小): 100
样本方差: 846.18
总体方差: 761.56
样本标准差: 29.09
总体标准差: 27.6
变异系数: 0.3817
指标怎么算什么时候用
极差最大值 − 最小值最快看一眼「跨度多大」,但中间的人差多少完全看不出来
方差每个数离平均值的差,平方后再平均数学上最常用,可单位是「平方分」,不好解释
标准差方差开平方和原数据同单位,「平均偏离多少分」,最好解释
变异系数标准差 ÷ 平均值两组数据量级不同时,比谁更「散」

为什么会有「样本 / 总体」两套?手上这组数就是全部(比如全班就这十个人)→ 用带 p 的那套;只是从更大的一群里抽出来的样本 → 用不带 p 的那套。样本方差 846.18 比总体方差 761.56 大一点,就是因为它分母上除以 n-1 而不是 n。数据越少,两者差得越明显。

6.6 分布位置:分位数与五数概括

只看最大值最小值太粗糙——中间的人过得怎么样?把数据排好队、切成几段来看,就是分位数:

Python
import statistics

scores = [88, 92, 79, 95, 61, 84, 90, 73, 100, 0]

q1, q2, q3 = statistics.quantiles(scores, n=4)
print('Q1 下四分位:', q1)
print('Q2 中位数:', q2)
print('Q3 上四分位:', q3)
print('四分位距 IQR:', round(q3 - q1, 2))
print('五数概括:', min(scores), '/', q1, '/', q2, '/', q3, '/', max(scores))

输出:

输出
Q1 下四分位: 70.0
Q2 中位数: 86.0
Q3 上四分位: 92.75
四分位距 IQR: 22.75
五数概括: 0 / 70.0 / 86.0 / 92.75 / 100

n=4 表示切成四段,返回三个切点。Q2 永远等于中位数,可以拿它自检。Q1 = 70 读作「约 25% 的人低于 70 分」,Q3 = 92.75 读作「约 75% 的人低于 92.75 分」。

Q3 − Q1 叫四分位距(IQR),它只看中间那 50% 的人,所以完全不受极端值影响——上面这组的极差是 100(有个 0 分),听着吓人;四分位距只有 22.75,说明「大部分同学的差距」其实没那么夸张。最小、Q1、中位数、Q3、最大这五个数合起来叫五数概括,画箱线图用的就是它。

6.7 变量之间的关系:相关与回归

前面都在描述「一组数据」,最后看「两组数据之间有没有关系」。比如:平时练得多的同学,期末是不是考得更好?

Python
import statistics

# 平时练习次数 vs 期末成绩
practice = [2, 3, 5, 6, 8, 9, 11, 12]
final = [55, 61, 68, 70, 78, 82, 88, 95]

print('相关系数 r:', round(statistics.correlation(practice, final), 3))

slope, intercept = statistics.linear_regression(practice, final)
print('回归直线:成绩 =', round(slope, 2), '× 练习次数 +', round(intercept, 2))
print('照这条线,练 10 次大概能考:', round(slope * 10 + intercept, 1))

输出:

输出
相关系数 r: 0.996
回归直线:成绩 = 3.74 × 练习次数 + 48.45
照这条线,练 10 次大概能考: 85.8

correlation() 给的是相关系数 r,永远落在 -1 到 1 之间:

r 的值怎么读
接近 1一个变大,另一个也跟着变大(正相关)
接近 -1一个变大,另一个反而变小(负相关)
接近 0看不出线性关系

0.996 已经非常接近「一条直线」,说明这 8 个人里,练习次数和成绩几乎同步变化。

linear_regression() 返回斜率和截距,也就是那条最贴合的直线。斜率 3.74 读作「多练一次,成绩平均提高约 3.74 分」。

两个新手最容易踩的坑:
① 相关不等于因果。r 高只说明「一起变」,不说明「因为练了所以考好」——也可能两者都是「学习态度好」的结果。
② 别往外推。这条线是用 2~12 次的数据拟合出来的,拿它预测「练 100 次能考 422 分」就荒唐了(满分才 100)。回归线只在数据的范围内用才靠谱。

6.8 指标速查表

把上面所有指标收成四张表,忘了就回来查。左边三个字母开头的 p,都表示「这组数就是全体」。

① 集中趋势(数据水平在哪)

指标函数一句话说明
算术平均mean(x)总和 ÷ 个数,最常用
算术平均(更快)fmean(x)总是返回小数;数据多时更快
加权平均fmean(x, weights=w)「平时分 30% + 期末 70%」这种
中位数median(x)排队后正中间那个,抗极端值
下 / 上中位数median_low(x) / median_high(x)偶数个数据时,取靠下 / 靠上的那一个
分组中位数median_grouped(x)数据本来就是分组区间时用
众数mode(x) / multimode(x)出现次数最多 / 并列最多的全部
几何平均geometric_mean(x)连乘场景:增长率、比率
调和平均harmonic_mean(x)平均速度、平均单价这类「每份总量相同」的场合

② 离散程度(数据散不散)

指标函数一句话说明
极差max(x) - min(x)最大减最小,最粗的「散不散」
样本方差variance(x)偏离平均值的平方的平均(除以 n-1)
总体方差pvariance(x)同上,但这组数就是全体(除以 n)
样本标准差stdev(x)方差开方,和原数据同单位,最好解释
总体标准差pstdev(x)同上,数据就是全体时用
变异系数stdev(x) / mean(x)量级不同的两组数据比离散程度

③ 分布位置(数据从哪到哪)

指标函数一句话说明
最小 / 最大min(x) / max(x)数据的两个端点
分位数quantiles(x, n=4)切成 n 段的切点;n=4 是四分位,n=10 是十分位
四分位距 IQRq3 - q1中间 50% 的人跨多大,抗极端值

④ 变量间关系(两组数据一起变吗)

指标函数一句话说明
相关系数correlation(x, y)-1 到 1,两条数据一起变的程度
回归直线linear_regression(x, y)返回(斜率, 截距),用来做预测
协方差covariance(x, y)相关的「未标准化版」,单位跟着数据走
偏度标准库没有数据往哪边歪 → 用 pandas 的 Series.skew()
峰度标准库没有数据尖不尖、尾巴厚不厚 → 用 pandas 的 Series.kurt()
想画分布、算正态概率,statistics 里还有 NormalDist(正态分布)和 kde(核密度),已经属于概率的范畴,等用到再翻。
statistics 只吃数字:拿空列表求平均会报 StatisticsError: mean requires at least one data point,只有一个数求标准差会报 StatisticsError: stdev requires at least two data points。算之前先确认「手里有几个数」。

课程思政|诚实与批判性思维 · 别只挑一个对自己有利的数

同一份数据,换个指标就能讲出不一样的故事:想说「我们班考得好」就报中位数,想说「我们班退步了」就报平均分。指标本身没有对错,只报一个、不说明口径,才是问题。将来你写报告、做汇报,数字怎么算、为什么这么算,都要经得起别人追问一句「这个数是怎么来的」。

七、random:抽样与可复现

random 的基础用法在《12-time库与random库》里已经讲过。本节只补做数据分析时才会用到的两个点:抽样、造模拟数据——以及让它们可复现。

7.1 随机抽几个人来检查

Python
import random

random.seed(42)          # 固定种子:以后每次跑,结果都一模一样
names = ['小满', '阿禾', '小鹿', '乐乐', '果果', '小北']

print('抽查 2 人:', random.sample(names, 2))
print('每天卖出的杯数:', [random.randint(40, 90) for _ in range(5)])

输出:

输出
抽查 2 人: ['小北', '小满']
每天卖出的杯数: [41, 87, 57, 55, 54]

random.seed(42) 是这一节的关键:种子一样,抽出来的名单和数字永远一样。你交上去的作业、写进报告的数字,别人照着跑能得到完全相同的结果,这才叫「可复现」。

7.2 没有数据?先造一批「看起来像真的」

Python
import random

random.seed(7)
# gauss(平均值, 标准差):造一批符合正态分布的模拟数据
print('模拟 6 位同学的分数:', [round(random.gauss(78, 10)) for _ in range(6)])

输出:

输出
模拟 6 位同学的分数: [75, 83, 76, 75, 69, 76]

random.gauss(平均值, 标准差) 生成的是正态分布的随机数:大部分落在平均值附近,越往两边越少。成绩、身高、销量大多是这个形状。真实数据还没拿到时,先用它造一批来练,代码就能先跑通。

忘了写 seed(),每次跑的结果都不一样,「贴出来给别人看」的输出自然对不上。规矩很简单:凡是要写进报告、作业、教程的随机结果,前面都先加一行 random.seed(数字)。

八、time:时间戳与给代码计时

time 在《12-time库与random库》里讲过让程序等一下(sleep)和格式化日期。做数据分析时,它还常用来干两件事:给数据盖上时间戳、给代码计时。

8.1 时间戳:给数据记上「什么时候存的」

Python
import time

now = time.time()          # 现在的时间戳:一个浮点数
stamp = 1735689600         # 一个具体的时间戳

print('时间戳的类型:', type(now))
print('这个时间戳对应:', time.strftime('%Y-%m-%d %H:%M', time.gmtime(stamp)))

输出:

输出
时间戳的类型: <class 'float'>
这个时间戳对应: 2025-01-01 00:00
注意 gmtime() 按 UTC(零时区)换算,比北京时间少 8 小时:这个时间戳对应的北京时间是 2025-01-01 08:00。想要手机上的本地时间,把 gmtime 换成 time.localtime() 就行,时间戳本身不变。

时间戳就是「从 1970-01-01 00:00 起过了多少秒」,本质是一个数字。存数据时一律存时间戳,要给人看时再用 strftime 转成日期——这样跨时区、换电脑都不会乱。

8.2 给代码计时:用 perf_counter()

Python
import time

t0 = time.perf_counter()
total = sum(range(1000001))
t1 = time.perf_counter()

print('1 加到 100 万:', total)
print('耗时不到 1 秒:', t1 - t0 < 1)

输出:

Python
1 加到 100 万: 500000500000
耗时不到 1 秒: True

套路就三步:开始前记一次 t0、干完活记一次 t1、两个一减就是耗时。为什么用它而不是 time()?perf_counter() 精度更高,也不受「系统时间被人调过」的影响。数据多到跑不动时,就靠它找出到底是哪一段慢。

具体秒数每次跑都不一样(换台电脑差得更多),所以上面那段只判断「有没有超过 1 秒」,页面里也就不写具体数字。计时结果别当作固定结论写进报告,真要写就写「约 0.0x 秒」,并注明是在什么机器上测的。
常用函数管什么
time.time()现在的时间戳(浮点数,单位秒)
time.perf_counter()高精度计时器,专门用来量「跑了多久」
time.sleep(秒)让程序停一会儿(做倒计时、控制请求频率)
time.gmtime(时间戳)把时间戳拆成年月日时分秒
time.strftime(格式, 时间)格式化成能看懂的日期字符串

九、collections 与 itertools:数数、分组、组合

最后两个「顺手工具」。它们不是非用不可,但用上之后,很多绕来绕去的循环会短一大截。

9.1 Counter:自动数谁出现得最多

Python
from collections import Counter, defaultdict

orders = ['珍珠奶茶', '椰果奶茶', '珍珠奶茶', '柠檬茶', '珍珠奶茶']

print('点单排行:', Counter(orders).most_common())

# defaultdict(int):分类累加时不用先问「这个键在不在」
spend = defaultdict(int)
for item, price in [('奶茶', 12), ('奶茶', 15), ('炸鸡', 25)]:
    spend[item] += price
print('分类合计:', dict(spend))

输出:

输出
点单排行: [('珍珠奶茶', 3), ('椰果奶茶', 1), ('柠檬茶', 1)]
分类合计: {'奶茶': 27, '炸鸡': 25}

Counter 是「会自动数数的字典」:塞进去一串东西,它直接告诉你每样出现了几次,most_common() 还会按次数从多到少排好——词频统计、投票、点单排行都是它。defaultdict(int) 则是「分类累加」的省事写法:不用先判断键在不在,直接 spend[item] += price。第 26 篇《文件处理》的词频案例用的就是 Counter。

9.2 itertools:组合、排列交给它

Python
from itertools import combinations

# 三个人两两握手,一共几次?——组合枚举
people = ['小满', '阿禾', '小鹿']

print('两两一组:', list(combinations(people, 2)))

输出:

输出
两两一组: [('小满', '阿禾'), ('小满', '小鹿'), ('阿禾', '小鹿')]

自己写两层嵌套循环算组合也能算,但容易写错、还容易漏。常用的几个:combinations(列表, k) 取组合(不管顺序)、permutations(列表, k) 取排列(管顺序)、chain(a, b) 把两串接成一串、product(a, b) 出笛卡尔积(比如「尺码 × 颜色」的所有搭配)。

十、再往前一步:numpy / pandas / openpyxl / matplotlib

前面九节用的都是「装好就有」的标准库。真做数据分析,还会碰上几个名字:numpy、pandas、matplotlib,还有专门读写 Excel 的 openpyxl。它们不是标准库,要自己装(第 11 篇《导入库的方法》讲过「标准库 vs 第三方库」的区别)。

库管什么什么时候该上它
numpy一整列数字一起算(数组、矩阵)数据几千几万行,逐行循环太慢
pandas带行列名的表格(DataFrame)要按列筛、按班级分组、做汇总
matplotlib画图:折线、柱状、散点要把结果画出来给人看
openpyxl读写 Excel 的 .xlsx 文件别人发来的表格是 .xlsx,导出的报表也要 .xlsx

装它们,在终端里执行这一行(虚拟环境的做法见第 27 篇《模块与包》第八节,建议装进项目自己的环境里):

命令
pip install numpy pandas matplotlib openpyxl

下面几段的首行都写了 # 示意——它们本身就是标准 Python 代码,只是要先装库才能跑,所以这里只讲「长什么样、好在哪」,不贴具体输出。

10.1 numpy:整个数组一起算

Python
# 示意:要先 pip install numpy 才能跑
import numpy as np

scores = np.array([88, 92, 79, 95, 61])
print(scores.mean())
print(scores + 5)          # 每个元素一起加 5

卖点是「整个数组一起算」:scores + 5 表示每个元素都加 5,不用写循环;scores.mean() 直接出平均分。数据到几万行时,「用不用 numpy」就是「跑得动」和「跑不动」的区别。

10.2 pandas:像 Excel 一样的表格

Python
# 示意:要先 pip install pandas 才能跑
import pandas as pd

df = pd.read_csv('scores.csv')
print(df.groupby('班级')['总分'].mean())

同一件活,标准库写法和 pandas 写法差多少,放在一起就看清了:

要干的事标准库写法pandas 写法
读一张 csvcsv.DictReader(文件对象)pd.read_csv('scores.csv')
某一列求平均statistics.mean([int(r['总分']) for r in rows])df['总分'].mean()
按班级分组求平均循环 + defaultdict(list) + meandf.groupby('班级')['总分'].mean()
某一列求和sum(int(r['总分']) for r in rows)df['总分'].sum()
排个名sorted(rows, key=..., reverse=True)df.sort_values('总分', ascending=False)
画一张柱状图标准库画不了df['总分'].plot.bar()

10.3 openpyxl / pandas:读写 Excel 的 .xlsx

第四节说过:.csv 是纯文本,标准库就能读写;.xlsx 不行——它本质是个 zip 压缩包,标准库解析不了。要处理它,装 openpyxl:

Python
# 示意:要先 pip install openpyxl 才能跑
import openpyxl

wb = openpyxl.load_workbook('成绩单.xlsx')       # 打开整个工作簿
ws = wb['统计一班']                              # 选中一张工作表
for row in ws.iter_rows(min_row=2, values_only=True):
    print(row)                                   # 每行一个元组

三个概念对上就能用了:工作簿(整个 .xlsx 文件)→ 工作表(底下那排标签页)→ 单元格。iter_rows() 一行一行往下走,min_row=2 跳过表头,values_only=True 表示只要值、不要单元格对象。

Python
# 示意:要先 pip install openpyxl 才能跑
import openpyxl

wb = openpyxl.Workbook()                 # 新建一个空工作簿
ws = wb.active                           # 用它默认那张工作表
ws.append(['姓名', '分数'])               # append 一整行
ws.append(['小满', 92])
ws.append(['阿禾', 78])
wb.save('新成绩单.xlsx')                  # 存盘

装了 pandas 就更省事,读写各一行,而且直接给你 DataFrame:

Python
# 示意:要先 pip install pandas openpyxl 才能跑
import pandas as pd

df = pd.read_excel('成绩单.xlsx', sheet_name='统计一班')
print(df.head())
df.to_excel('汇总.xlsx', index=False)     # index=False:别把行号也写进表里
pandas 自己并不解析 Excel,它是在背后调 openpyxl(读 .xlsx)或 xlrd(读老的 .xls)。所以「read_excel 报缺库」,要装的是 openpyxl,不是 pandas 本身。

10.4 matplotlib:把结果画出来

Python
# 示意:要先 pip install matplotlib 才能跑
import matplotlib.pyplot as plt

plt.plot(['一', '二', '三', '四'], [12, 15, 9, 18])
plt.title('一周奶茶销量')
plt.show()

跑完会弹出一张折线图。三步记住就行:plot() 给数据点、title() 起名字、show() 显示出来。中文标题如果显示成一串方框,就指定一下中文字体(Mac 用 Heiti TC,Windows 用 SimHei)。

这三个库都装在你自己这台电脑上:机房、考试电脑不一定有,别人拿到你的代码也得先装一遍。所以——能用标准库算清楚就先用手里的标准库,数据大到算不动、或者要画图,再上它们。标准库永远是保底的那一套。
建议路线:先用 csv + statistics 把「读数据 → 算指标 → 出报表」整条路走通(第十一节就是这件事),再上 pandas。一上手就用 pandas,很容易连「数据到底长什么样、每个字段什么意思」都没看清。

AI 助手小贴士|标准库写法 ↔ pandas 写法,最适合对照着学。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:把下面这段用 csv 和 statistics 写的统计代码改成 pandas 写法,并逐行标注两边的对应关系:<贴上你的代码>

十一、合起来用:一份会自己记日期的成绩表

把前面学的串一条线:用 pathlib 管路径、csv 存表格、datetime 记时间、json 存汇总。

Python
# 文件:save_scores.py
import csv, json
from datetime import datetime
from pathlib import Path

def save(folder, scores):
    folder = Path(folder)
    folder.mkdir(parents=True, exist_ok=True)

    # 1) 明细存成 csv,Excel 能直接打开
    csv_path = folder / 'scores.csv'
    with open(csv_path, 'w', encoding='utf-8-sig', newline='') as fp:
        w = csv.writer(fp)
        w.writerow(['姓名', '分数'])
        w.writerows(scores)

    # 2) 汇总存成 json,带生成时间
    total = sum(s for _, s in scores)
    summary = {
        '生成时间': datetime.now().strftime('%Y-%m-%d %H:%M'),
        '人数': len(scores),
        '平均分': round(total / len(scores), 1),
    }
    json_path = folder / 'summary.json'
    json_path.write_text(
        json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')

    return csv_path, json_path

用法和效果:

Python
import csv, json, pathlib, tempfile
from pathlib import Path

work = Path(tempfile.mkdtemp())
scores = [('小满', 92), ('阿禾', 78), ('小鹿', 85)]

# 写明细
csv_path = work / 'scores.csv'
with open(csv_path, 'w', encoding='utf-8-sig', newline='') as fp:
    w = csv.writer(fp)
    w.writerow(['姓名', '分数'])
    w.writerows(scores)

# 写汇总
total = sum(s for _, s in scores)
summary = {'人数': len(scores), '平均分': round(total / len(scores), 1)}
(work / 'summary.json').write_text(
    json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')

# 再读回来验证一遍
with open(csv_path, 'r', encoding='utf-8', newline='') as fp:
    rows = list(csv.DictReader(fp))
print('从 csv 读回', len(rows), '条记录')
print('第一条:', rows[0])
print('从 json 读回:', json.loads((work / 'summary.json').read_text(encoding='utf-8')))

输出:

输出
从 csv 读回 3 条记录
第一条: {'\ufeff姓名': '小满', '分数': '92'}
从 json 读回: {'人数': 3, '平均分': 85.0}

这一套「写出去能被人打开、读回来能继续算」的模式,就是实际项目里最常用的数据落地方式。

需求选哪个原因
要给人看、要能用 Excel 打开csv表格结构,双击就开
要存复杂的嵌套结构json能装字典套列表
只存一行行纯文本直接读写 txt最简单,第 26 篇讲过
要给数据加时间标记datetime格式化 + 算差值

课程思政|数据安全与法治素养 · 会落地,也要会看管

这一节把 pathlib、csv、datetime、json 串成一条线,让一份成绩表既能存下来,又能读回来接着算。工具顺手了,责任也跟着来了:同学的成绩、联系方式都属于个人信息,能随手续写,不等于能随便留存和传阅。自己练手就用脱敏或假数据,将来在工作里也先问一句「这份数据我有没有权限存」。

十二、常见坑

坑 1:忘了写 encoding='utf-8',中文乱码或直接报错

Python
import pathlib, tempfile
work = pathlib.Path(tempfile.mkdtemp())
f = work / 'gbk.txt'
f.write_bytes('中文'.encode('gbk'))      # 假设这是别的软件存出来的文件
print(f.read_text(encoding='utf-8'))     # 按 utf-8 去读

运行后会报错(逐字照抄解释器):

报错
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte

处理办法:读写文件一律显式写 encoding='utf-8'。读到老软件存的 GBK 文件时,试 encoding='gbk'。

坑 2:csv 写出空行

Python
import csv

# 错的写法:漏了 newline='',Windows 上每行后面会多出一个空行
with open('a.csv', 'w', encoding='utf-8') as fp:
    csv.writer(fp).writerow(['a', 'b'])

# 对的写法
with open('a.csv', 'w', encoding='utf-8', newline='') as fp:
    csv.writer(fp).writerow(['a', 'b'])

坑 3:csv 读出来的数字是字符串

Python
import csv, io

rows = list(csv.DictReader(io.StringIO('姓名,分数\n小满,92\n')))
score = rows[0]['分数']
print('值:', score, '|类型:', type(score).__name__)
print('算数前先转:', int(score) + 1)

输出:

输出
值: 92 |类型: str
算数前先转: 93

处理办法:参与计算前先 int() 或 float() 转一下,和 input() 的处理方式一样。

坑 4:json 文件里中文变成 \u4e2d

Python
import json

print(json.dumps({'城市': '广州'}))
print(json.dumps({'城市': '广州'}, ensure_ascii=False))

输出:

输出
{"\u57ce\u5e02": "\u5e7f\u5dde"}
{"城市": "广州"}

处理办法:加 ensure_ascii=False。两种都能被 loads() 读回来,加参数只是为了「人看着舒服」。

坑 5:手写路径拼接

Python
# 别这么写:换个系统就失效
path = 'data' + '/' + 'a.csv'

# 这么写:pathlib 会自动用对分隔符
from pathlib import Path
path = Path('data') / 'a.csv'

坑 6:json.dumps 和 json.dump 用混

Python
import json
from pathlib import Path

data = {'姓名': '小满', '分数': 92}

# dumps(带 s)= 变成字符串,返回值给你,需要自己存
text = json.dumps(data, ensure_ascii=False)
Path('a.json').write_text(text, encoding='utf-8')

# dump(不带 s)= 直接写进文件对象
with open('a.json', 'w', encoding='utf-8') as fp:
    json.dump(data, fp, ensure_ascii=False)

口诀:带 s 的是「转成字符串」,不带 s 的是「直接对文件操作」。其余几个也同理:loads / load。

坑 7:把字符串丢给 statistics 去算

Python
import statistics

print(statistics.mean(['88', '92', '95']))

运行后会报错(逐字照抄解释器):

报错
TypeError: can't convert type 'str' to numerator/denominator

处理办法:和「csv 读出来的数字是字符串」是同一个病根——算之前先转:statistics.mean([int(s) for s in data])。

坑 8:只有一个数就求标准差

Python
import statistics

print(statistics.stdev([88]))

运行后会报错(逐字照抄解释器):

报错
statistics.StatisticsError: stdev requires at least two data points

处理办法:「散不散」至少要两个数才比得出来。算之前先判断 if len(data) >= 2:,不够就先不算,或者直接提示「样本太少」。

坑 9:拿列表去数数

Python
from collections import Counter

print(Counter([['奶茶'], ['炸鸡']]))

运行后会报错(逐字照抄解释器):

报错
TypeError: unhashable type: 'list'

处理办法:Counter 数的东西必须是「能当字典键的」(字符串、数字、元组)。要数一组一组的组合,先把每组转成 tuple(单品) 或拼成一个字符串再数。

坑 10:把 .xlsx 当作 csv 去读

Python
import csv, pathlib, tempfile, zipfile

work = pathlib.Path(tempfile.mkdtemp())
p = work / '成绩单.xlsx'
with zipfile.ZipFile(p, 'w', zipfile.ZIP_STORED) as z:   # .xlsx 的本质就是一个 zip 压缩包
    z.writestr('sheet1.xml', bytes(range(256)))

print('后缀明明是 .xlsx,开头两个字节却是:', p.read_bytes()[:2])

try:
    with open(p, 'r', encoding='utf-8') as fp:
        list(csv.reader(fp))
except UnicodeDecodeError:
    print('用 csv 打开它 → 报错 UnicodeDecodeError(根本不是文本,读不了)')

输出:

输出
后缀明明是 .xlsx,开头两个字节却是: b'PK'
用 csv 打开它 → 报错 UnicodeDecodeError(根本不是文本,读不了)

处理办法:认后缀。b'PK' 就是 zip 压缩包的标志(P / K 取自 zip 格式发明人 Phil Katz 的首字母),而 .xlsx 正是 zip。这类文件只能交给 openpyxl 或 pandas.read_excel()(第十节 10.3)——改编码、改后缀都没用,里面的字节一个都没变。

十三、报错手册

报错原文都是从本机解释器逐字抄下来的,对照着找就行。

报错原文(最后一行)什么情况怎么改
FileNotFoundError: [Errno 2] No such file or directory: '没有的文件.txt'要读的文件不存在(路径写错 / 文件没放对地方)用 Path(...).exists() 先判断,或核对路径
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)JSON 格式不合法(常见:用了单引号、多了逗号)JSON 里键和字符串必须双引号;用在线工具或 json.dumps 生成
TypeError: Object of type set is not JSON serializable拿集合(或函数、自定义对象)去 json.dumps先转成列表:list(集合)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte文件不是 UTF-8 编码(常见是老软件存的 GBK)试 encoding='gbk';或先把文件转成 UTF-8
ValueError: time data '2026/10/04' does not match format '%Y-%m-%d'字符串和格式串对不上(/ 对 -)格式串要写成 '%Y/%m/%d' 才能解析斜杠
AttributeError: 'str' object has no attribute 'exists'把字符串当路径对象用先 p = Path('a.txt'),再 p.exists()
statistics.StatisticsError: mean requires at least one data point拿空列表去求平均先判断 if data:,空的时候给个默认值
statistics.StatisticsError: stdev requires at least two data points只有一个数就求标准差标准差至少要两个数据点,先 if len(data) >= 2:
TypeError: can't convert type 'str' to numerator/denominator把字符串列表丢给 statistics.mean()先 int() / float() 转成数字再算
ValueError: Sample larger than population or is negativerandom.sample(名单, 10) 要的数量比名单人数还多抽的数量 k 不能大于列表长度
TypeError: unhashable type: 'list'往 Counter 里塞了列表先转成 tuple 或字符串
ModuleNotFoundError: No module named 'numpy'没装就 import numpy(第三方库不是自带的)在终端执行 pip install numpy;已装还报,多半是装到了别的 Python 环境
zipfile.BadZipFile: File is not a zip file把 .csv 改名成 .xlsx 就去打开了——改后缀不会换格式,里面还是纯文本;读 .xlsx 要用 openpyxl
ModuleNotFoundError: No module named 'openpyxl'用了 pandas.read_excel() 或 openpyxl 但没装pip install openpyxl(老式的 .xls 则是 pip install xlrd)
标准差比「手算」小了一点点用了 pstdev(总体),而不是 stdev(样本)数据是抽来的样本就用 stdev;这组数就是全体才用 pstdev
相关系数很高,就断定「A 导致 B」相关不等于因果,r 高只说明「一起变」要下因果结论得靠实验设计,别用相关系数硬推

下面这几条是「不报错,但结果不对」的,比报错更值得当心:

现象原因怎么改
CSV 每行后面多一个空行少了 newline=''写和读都加上 newline=''
CSV 读出来的数字没法直接相加读进来的是字符串先 int() / float()
JSON 文件里中文全是 \u…没加 ensure_ascii=False加上这个参数
Excel 打开 CSV 中文乱码编码不是 Excel 认的那一种存的时候用 encoding='utf-8-sig'
Windows 上路径报错,macOS 上好好的路径里的分隔符写死了改用 pathlib 的 / 拼
平均分和中位数差很多数据里有极端值(0 分、超高的个例)两个都算出来,报告里一并说明
每次跑出来的随机结果都不一样,输出对不上忘了写 random.seed()需要复现时,先固定种子再生成
页面上的耗时和我不一样计时结果本来就因机器而异只比数量级,别当固定值写进结论

十四、十六个基础案例

下面十六个例子都很短,建议每个都亲手敲一遍、跑一遍。后六个是统计、抽样、计时、计数,以及表格文件的几种写法。

1. 拆一个路径的各个部分

Python
from pathlib import Path
p = Path('data/2026/scores.csv')
print(p.name, '|', p.stem, '|', p.suffix, '|', p.parent)

输出:

输出
scores.csv | scores | .csv | data/2026

2. 判断文件在不在,再决定读不读

Python
import pathlib, tempfile
f = pathlib.Path(tempfile.mkdtemp()) / 'a.txt'
if f.exists():
    print('能读:', f.read_text(encoding='utf-8'))
else:
    print('文件不在,先跳过')

输出:

输出
文件不在,先跳过

3. 一行写、一行读

Python
import pathlib, tempfile
f = pathlib.Path(tempfile.mkdtemp()) / 'note.txt'
f.write_text('今天学了标准库', encoding='utf-8')
print(f.read_text(encoding='utf-8'))

输出:

输出
今天学了标准库

4. 用 json 存一份带嵌套结构的配置

Python
import json
cfg = {'班级': '统计一班', '人数': 30, '课程': ['Python', '统计']}
text = json.dumps(cfg, ensure_ascii=False, indent=2)
print(text)
print('人数:', json.loads(text)['人数'])

输出:

Python
{
  "班级": "统计一班",
  "人数": 30,
  "课程": [
    "Python",
    "统计"
  ]
}
人数: 30

5. 用 csv 写一张能被 Excel 打开的表

Python
import csv, pathlib, tempfile
f = pathlib.Path(tempfile.mkdtemp()) / 'scores.csv'
with open(f, 'w', encoding='utf-8-sig', newline='') as fp:
    w = csv.writer(fp)
    w.writerow(['姓名', '分数'])
    w.writerow(['小满', 92])
print('写完之后,文件里的内容是:')
print(f.read_text(encoding='utf-8-sig'))

输出:

输出
写完之后,文件里的内容是:
姓名,分数
小满,92

6. 用 DictReader 按字段名取数据并求和

Python
import csv, io
text = '姓名,分数\n小满,92\n阿禾,78\n小鹿,85\n'
total = sum(int(r['分数']) for r in csv.DictReader(io.StringIO(text)))
print('总分:', total)

输出:

输出
总分: 255

7. 把当前时间格式化成一行日志

Python
from datetime import datetime
d = datetime(2026, 10, 4, 19, 30, 0)
print('[%s] 程序开始运行' % d.strftime('%Y-%m-%d %H:%M:%S'))

输出:

输出
[2026-10-04 19:30:00] 程序开始运行

8. 算两个日期差多少天

Python
from datetime import date
start = date(2026, 9, 1)
end = date(2026, 10, 4)
print('开学到今天:', (end - start).days, '天')

输出:

输出
开学到今天: 33 天

9. 把学生名单按「姓名-分数」两列存成 json

Python
import json
scores = [('小满', 92), ('阿禾', 78)]
data = [{'姓名': n, '分数': s} for n, s in scores]
print(json.dumps(data, ensure_ascii=False))

输出:

输出
[{"姓名": "小满", "分数": 92}, {"姓名": "阿禾", "分数": 78}]

10. 目录不存在就先建出来

Python
import pathlib, tempfile
from pathlib import Path
base = Path(tempfile.mkdtemp())
target = base / '学期项目' / '第一周'
target.mkdir(parents=True, exist_ok=True)
print('文件夹建好了吗:', target.exists())
print('最后一级是:', target.name)

输出:

输出
文件夹建好了吗: True
最后一级是: 第一周

11. 算一组成绩的平均分、中位数和标准差

Python
import statistics

scores = [72, 68, 91, 85, 77, 80]

print('平均分:', round(statistics.mean(scores), 1))
print('中位数:', statistics.median(scores))
print('标准差:', round(statistics.stdev(scores), 2))

输出:

输出
平均分: 78.8
中位数: 78.5
标准差: 8.42

12. 固定种子,随机抽查 3 个人

Python
import random

random.seed(2026)
students = ['小满', '阿禾', '小鹿', '乐乐', '果果', '小北', '汤圆', '芋圆']

print('被抽到的 3 人:', random.sample(students, 3))

输出:

输出
被抽到的 3 人: ['阿禾', '小鹿', '果果']

13. 给一段代码计时

Python
import time

t0 = time.perf_counter()
total = sum(i * i for i in range(1, 100001))
t1 = time.perf_counter()

print('平方和:', total)
print('耗时不到 1 秒:', t1 - t0 < 1)

输出:

Python
平方和: 333338333350000
耗时不到 1 秒: True

14. 数一数每样加料被点了几次

Python
from collections import Counter

words = '珍珠 椰果 珍珠 布丁 椰果 珍珠 西米 椰果'.split()

print('每样加料被点了几次:', Counter(words).most_common())
print('加料种数:', len(set(words)))

输出:

输出
每样加料被点了几次: [('珍珠', 3), ('椰果', 3), ('布丁', 1), ('西米', 1)]
加料种数: 4

AI 助手小贴士|标准库真正的用法是「组合拳」,出组合题练。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:出 2 道标准库组合题:①用 datetime 和 os 把文件夹里的照片按月份归类;②用 json 和 random 做随机抽签并保存记录。只要思路提示

15. 写一个 Excel 也能打开的 tsv

Python
import csv, io

buf = io.StringIO()
w = csv.writer(buf, delimiter='\t')          # 用制表符分隔
w.writerow(['姓名', '分数', '班级'])
w.writerow(['小满', 92, '统计一班'])
w.writerow(['阿禾', 78, '统计一班'])
print(repr(buf.getvalue()))

输出:

输出
'姓名\t分数\t班级\r\n小满\t92\t统计一班\r\n阿禾\t78\t统计一班\r\n'

16. 一次算齐一组成绩的全部指标

Python
import statistics

scores = [88, 92, 79, 95, 61, 84, 90, 73, 100, 0]
q1, q2, q3 = statistics.quantiles(scores, n=4)

print('人数:', len(scores))
print('平均:', statistics.mean(scores), '|中位:', statistics.median(scores))
print('最低:', min(scores), '|最高:', max(scores), '|极差:', max(scores) - min(scores))
print('标准差:', round(statistics.stdev(scores), 2),
      '|变异系数:', round(statistics.stdev(scores) / statistics.mean(scores), 4))
print('五数概括:', min(scores), '/', q1, '/', q2, '/', q3, '/', max(scores))

输出:

输出
人数: 10
平均: 76.2 |中位: 86.0
最低: 0 |最高: 100 |极差: 100
标准差: 29.09 |变异系数: 0.3817
五数概括: 0 / 70.0 / 86.0 / 92.75 / 100

十五、常见问题

Q:os.path 和 pathlib 到底用哪个?
新写的代码用 pathlib:写法更短、跨系统更稳、有 / 可以拼路径。但老代码和老教程里全是 os.path,必须看得懂。两个混着用也可以,因为 Path() 能接字符串、str(路径) 也能变回字符串。

Q:数据存 csv 还是 json?
一句话判断:长得像一张表(每行同几个字段)→ csv;长得像一棵树(字典套列表、层层嵌套)→ json。要给人用 Excel 打开,选 csv。

Q:为什么我的 csv 用 Excel 打开中文乱码?
Excel 默认按本地编码猜。解决:写文件时用 encoding='utf-8-sig'(多了 BOM 标记,Excel 一看就知道是 UTF-8)。

Q:json.dumps 和 json.dump 我总是记混?
只记一点:带 s 的那个 s = string(字符串)。所以 dumps 返回字符串、loads 吃字符串;不带的就直接对着文件对象。四个函数按这个规律一拆就清楚。

Q:datetime.now() 每次结果都不一样,怎么测试?
这正是要小心的地方。写测试或写教程时,用固定时间 datetime(2026, 10, 4);真实程序里用 now()。别把 now() 的结果写进需要可重复比对的输出里。

Q:这几个库要不要背?
不用。这一篇的作用是知道「有这些东西、能解决什么问题」,具体函数名忘了就查:dir(模块) 看有什么,help(函数) 看怎么用(第 27 篇《模块与包》第四节讲过)。真正需要背下来的只有「json.dumps 带 s 是转字符串」「csv 要写 newline=''」这类会踩坑的点。

Q:算平均分,该用 statistics 还是自己写循环?
都行,但优先用 statistics:一行写完、还顺带算标准差和中位数;自己写循环容易漏掉「空列表」「字符串没转数字」这两种情况。只有一件事必须自己判断——用平均还是用中位数,这不是工具问题,是口径问题。

Q:numpy / pandas 要不要现在就学?
先把标准库这一套用顺再学。判断标准很简单:数据小到能一眼看完(几十行),标准库够用;数据大到要滚动条才能看完,或者要画图,才值得上 pandas。工具是跟着需求长的,先有需求再学,不容易忘。

Q:随机抽样的结果能写进实验报告吗?
能,但要把种子一起写上:写「用 random.seed(2026) 固定种子,随机抽了 3 人」,别人才能复现。只写「随机抽了 3 人」,别人跑出来是另一批人,这份报告就没法验证。

Q:csv 和 xlsx 到底该用哪个?
一句话:要「交换」用 csv,要「交付」用 xlsx。csv 是纯文本,谁的 Python 都能直接读,出问题用记事本当场就能看一眼;xlsx 能存格式、公式、多个工作表,但读它必须先装库,而且它本质是个压缩包,出问题肉眼看不出来。给人传数据、喂给别的程序——csv;交一份带表头样式、多张工作表的正式报表——xlsx。

Q:描述性统计要报哪几个指标?
至少三个:一个「中心」(平均数 + 中位数一起报)、一个「离散」(标准差)、一个「范围」(最小 / 最大)。只报平均数是最常见的偷懒——同一份数据换个指标就能讲出另一个故事。数据里有明显极端值,或者要和别人比「谁更整齐」,再把四分位距和变异系数补上。

Q:pandas.read_excel() 提示缺 openpyxl,怎么办?
照提示装:pip install openpyxl。pandas 自己不解析 Excel,它只负责指挥,真正干活的是 openpyxl(读 .xlsx)或 xlrd(读老的 .xls)。装完还报,多半是装到了别的 Python 环境——和 ModuleNotFoundError: No module named 'numpy' 是同一类问题。

AI 助手小贴士|标准库的问题是「不知道有什么」。把需求说给 AI,让它推荐该用哪些模块、各自负责什么——先要思路,再要代码。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:我想写一个程序:自动整理下载文件夹,把照片按拍摄月份分类到子文件夹。应该用到哪些标准库?每个模块负责什么?先给思路再给代码

十六、随堂自测(16 题)

点击选项即可判分,答错的题会立刻显示解析。

已答 0 / 18 · 正确 0 —

1要把 'data/scores.csv' 里的文件名、后缀、所在文件夹分别取出来,下面哪个库最方便?

  • A. pathlib,用 Path(p).name / .suffix / .parent
  • B. json
  • C. csv
  • D. datetime
答案:A。pathlib 专管路径:.name 文件名、.stem 去掉后缀、.suffix 后缀、.parent 所在文件夹。它还能用 / 拼路径,自动适配 Windows 和 macOS 的分隔符。

2用 pathlib 拼出 data 文件夹下的 a.csv,写法正确的是?

  • A. Path('data') + 'a.csv'
  • B. Path('data') / 'a.csv'
  • C. Path('data').join('a.csv')
  • D. Path('data', 'a.csv', sep='/')
答案:B。Path 重载了除法运算符 / 来拼路径,这是它的标志性写法,而且会自动用对当前系统的分隔符。+ 会报类型错误,也没有 join 这个方法。

3json.dumps(data, ensure_ascii=False) 里的 ensure_ascii=False 有什么作用?

  • A. 让 JSON 能被 Excel 打开
  • B. 让 JSON 自动按字母排序
  • C. 让中文直接显示,而不是变成 \u4e2d 这样的编码
  • D. 让数据压缩得更小
答案:C。默认情况下非 ASCII 字符会被转成 \uXXXX 转义,功能没问题但人没法读。加 ensure_ascii=False 后中文原样保存——占的字节还更少。要和 Excel 打交道应该用 csv,不是 json。

4下面哪个东西不能直接转成 JSON?

  • A. 字典 {'a': 1}
  • B. 集合 {1, 2}
  • C. 列表 [1, 2]
  • D. 布尔 True
答案:B。JSON 只认「对象、数组、字符串、数字、真值、空值」。集合会报 TypeError: Object of type set is not JSON serializable,要先 list(集合) 转成列表。另外元组虽然能转,但读回来会变成列表。

5用 csv 写文件时,open() 里必须加的参数是?

  • A. encoding='gbk'
  • B. errors='ignore'
  • C. buffering=0
  • D. newline=''
答案:D。不加 newline='',csv 自己写的换行会和 open() 的换行叠加,Windows 上每行后面多一个空行——这是 csv 最经典的坑,读和写都建议加上。

6用 csv.DictReader() 读出来的每一行是什么?

  • A. 一个列表,按列的位置取值
  • B. 一个字符串,整行原样
  • C. 一个字典,用第一行当字段名
  • D. 一个元组,不能修改
答案:C。DictReader 会拿第一行当字段名,之后每行给你一个字典,用 row['姓名'] 这种写法取值,比 row[0] 清楚得多,列顺序变了也不受影响。csv.reader() 才是返回列表的那个。

7用 csv 读进来的分数 row['分数'] 想直接参与计算,会怎样?

  • A. 它是字符串,要先 int() 或 float() 转换
  • B. 直接能算,csv 会自动转成数字
  • C. 它是分数对象,要用 .value 取值
  • D. 会报 TypeError
答案:A。csv 文件是纯文本,读进来的所有字段都是字符串,'88' 而不是 88。直接相加会得到 '88' + '95' 这种字符串拼接或者报错。先转类型再计算,和 input() 的处理一样。

8想按自定义格式显示时间,该用哪个方法?

  • A. strptime
  • B. strftime
  • C. format_time
  • D. tostring
答案:B。strftime 里的 f 是 format,把时间对象变成字符串;strptime 里的 p 是 parse,把字符串变成时间对象。两者只差一个字母,方向正好相反,是新手最容易记混的一对。

9datetime.strptime('2026/10/04', '%Y-%m-%d') 会怎样?

  • A. 正常返回 2026 年 10 月 4 日
  • B. 自动忽略符号差异,只按数字解析
  • C. 报 ValueError: time data '2026/10/04' does not match format '%Y-%m-%d'
  • D. 返回 None
答案:C。格式串里的分隔符必须和待解析的字符串一致:数据是斜杠就写 '%Y/%m/%d',是短横就写 '%Y-%m-%d'。报错信息里会同时打出数据和格式串,照着对比就能找出差在哪里。

10算「两个日期相差几天」,正确的做法是?

  • A. 两个 date 相减,取结果的 .days
  • B. 自己拿天数除以 30 估一个
  • C. 用 date.diff()
  • D. 把年份月份日期分别相减再换算
答案:A。两个日期相减得到 timedelta,.days 就是天数。月份、闰年、跨年这些麻烦 datetime 都替你算好了,自己用 30 天一个月去估一定会错。

11关于 json.dumps 和 json.dump,说法正确的是?

  • A. 两个完全一样,随便用
  • B. 不带 s 的只能处理列表
  • C. 带 s 的用于读取,不带 s 的用于写入
  • D. 带 s 的返回字符串,不带 s 的直接对文件对象操作
答案:D。把那个 s 记成 string 就好:dumps 把对象转成 JSON 字符串(返回值给你);dump 直接写进文件对象。loads / load 同理——一个吃字符串,一个吃文件。

12要存一份「每个学生有姓名、分数、以及多门课成绩」的嵌套数据,最合适的是?

  • A. csv,因为它是表格格式
  • B. 必须先转成集合再存
  • C. 直接写 txt,用逗号隔开
  • D. json,因为它能装字典套列表这种嵌套结构
答案:D。csv 是「一行一条、每行字段相同」的平面表,装不下「一个人对应多门课」这种嵌套。json 天生支持字典套列表,正适合这种结构。反过来,如果数据就是规规矩矩的表格、还想用 Excel 打开,那就该选 csv。

13一组成绩里有一位同学考了 0 分,想说明「大部分同学考得不错」,最该看哪个指标?

  • A. 平均分
  • B. 中位数
  • C. 最大值
  • D. 标准差
答案:B。0 分这种极端值会把平均分往下拽,却不能动摇中位数。同一组数据里,平均分 76.2、中位数 86.0,差得越多说明极端值影响越大。所以要两者一起看,别只报一个。

14random.seed(42) 的作用是什么?

  • A. 让随机数变得更大
  • B. 让随机数变成固定值(每次都是同一个数)
  • C. 把随机序列固定下来,之后每次运行都得到同一串「随机」结果
  • D. 提高随机性,避免重复
答案:C。种子固定后,随机数的序列就固定了:第一个、第二个、第三个都一模一样,所以整段程序每次跑的结果完全相同。没有它,输出每次都不一样,别人就没法复现你报告里的数字。

15要给一段代码计时,该用 time 里的哪个函数?

  • A. time.sleep()
  • B. time.strftime()
  • C. time.gmtime()
  • D. time.perf_counter()
答案:D。perf_counter() 是高精度计时器:开始前记一次、结束后记一次,两个一减就是耗时。它比 time.time() 精度高,也不受系统时间被改动的影响。sleep 是「停一会儿」,strftime / gmtime 是处理日期显示。

16关于 numpy、pandas、matplotlib,说法正确的是?

  • A. 它们是第三方库,要先 pip install 才能 import
  • B. 它们是标准库,装好 Python 就自带
  • C. 它们只能在 TRAE 里运行
  • D. 用了 pandas 就不需要再懂 csv 和字典了
答案:A。这三个都是第三方库,必须先装、而且装在哪个 Python 环境里也要对得上,否则会报 ModuleNotFoundError。不过 pandas 读的 csv、用的字典逻辑,正是本篇讲的那些东西——底层概念懂了,换工具只是换个写法。

17别人发来一份 .xlsx 表格,想用 Python 读它,下面说法正确的是?

  • A. 它是 zip 压缩包,标准库 csv 读不了,要装 openpyxl(或 pandas)
  • B. 把后缀名改成 .csv,标准库就能读了
  • C. 用 csv.DictReader 直接就能读
  • D. .xlsx 和 .csv 一样,都是纯文本
答案:A。.csv 是纯文本,标准库 csv 就能读写;.xlsx 本质是个 zip 压缩包(里面装着一堆 XML),标准库解析不了。改后缀更没用——里面的字节一个都没变,只会再报一个 BadZipFile。要读 .xlsx,得装 openpyxl 或用 pandas.read_excel()。

18想说明「这个班的成绩比隔壁班更参差」,最该拿哪个指标去比?

  • A. 两个班的平均分
  • B. 两个班的标准差
  • C. 两个班的最高分
  • D. 两个班的人数
答案:B。平均分说的是「水平」,标准差说的是「散不散」。两个班平均分都是 80,标准差 5 的那个整齐、标准差 20 的那个参差——这正是 statistics.stdev() 的用武之地。最高分只反映一个人,人数跟整齐程度无关。