查看全课大纲(第 11 / 51 节)
- 1.Python 是什么?为什么值得学?
- 2.如何为 Python 编程准备一个“全家桶”环境?
- 3.Python 有哪几种执行方法?
- 4.行与缩进到底有什么用?
- 5.注释有什么用,怎么写?
- 6.变量:如何给数据起个好名字?
- 7.运算符的优先级和常见坑点是什么?
- 8.Python 中,数字和布尔值究竟有哪几种?
- 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
- 10.如何将程序的结果展示给用户或保存下来?
- 11.CSV 文件怎么读怎么写才方便?
- 12.列表是什么,如何创建和访问它?
- 13.列表的修改操作,有哪些方法?
- 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
- 15.判断语句:如何让程序根据条件做不同的事?
- 16.for 循环如何遍历序列中的元素?
- 17.while 循环什么时候才会停?
- 18.while 循环的结束条件,你写对了吗?
- 19.循环中途想提前结束,怎么办?
- 20.`break` 和 `continue` 到底有什么区别?
- 21.列表推导式如何让代码更简洁高效?
- 22.字符串怎么定义、拼接和操作?
- 23.元组:为什么定义单个元素时要加逗号?
- 24.集合(set)怎么用?如何快速去重和做集合运算?
- 25.字典是什么,为什么比列表更方便?
- 26.字典的增删改查与遍历,如何避免 KeyError?
- 27.函数里的变量,为什么有的能用有的不能用?
- 28.在函数内部修改全局变量,为什么有时不生效?
- 29.函数里能修改全局变量吗?返回值怎么用?
- 30.匿名函数:如何用 lambda 快速定义简单函数?
- 31.面向对象编程是什么,为什么需要它?
- 32.类中的方法和属性到底怎么定义和使用?
- 33.如何控制对对象内部属性和方法的访问?
- 34.异常发生时,程序会怎样继续执行?
- 35.如何组织代码:包和模块
- 36.`if __name__ == '__main__'` 到底有什么用?
- 37.如何用 IDE 的 Debug 功能排查程序错误?
- 38.Matplotlib 绘图的第一步该做什么?
- 39.Figure 是什么,如何设置图形尺寸?
- 40.如何自定义 Matplotlib 的坐标轴?
- 41.如何为图表添加图例,让不同曲线一目了然?
- 42.饼图怎么画才能直观展示数据占比?
- 43.如何用 Matplotlib 绘制散点图?
- 44.柱状图怎么画?
- 45.如何使用 Matplotlib 绘制等高线图?
- 46.如何在 Matplotlib 中绘制子图?
- 47.zip 函数如何把多个列表“打包”在一起?
- 48.如何给可迭代对象加上索引?
- 49.random 模块如何生成随机数?
- 50.math 模块怎么用?
- 51.Counter 如何快速统计元素出现次数?
CSV 文件怎么读怎么写才方便?
约 14 分钟
小象实战讲义 · Python 编程从入门到实践
在数据处理中,我们经常遇到 CSV(Comma-Separated Values,逗号分隔值)格式的文件。它用纯文本存储表格数据,每行一条记录,字段间用逗号分隔。如果直接用普通文件读写,得到的是字符串,需要手动切分才能获取具体数值,非常不便。本节将学习 Python 标准库中的 csv 模块,它能将 CSV 文件的行自动转换为列表或字典,让数据访问变得简单直接。这是从基础文件操作迈向结构化数据处理的重要一步。
💡 核心导读
- CSV 是什么:一种用逗号分隔字段的纯文本表格格式,广泛用于数据交换。
- 用
csv.reader读取:将 CSV 每行读成一个列表,通过下标访问字段。 - 用
csv.writer写入:将列表的列表(二维列表)逐行写入 CSV 文件。 - 用
csv.DictReader读取:将 CSV 每行读成一个字典,通过列名(键)访问字段,更直观。 - 用
csv.DictWriter写入:将字典的列表写入 CSV 文件,需指定列名(表头)。
CSV 格式简介
CSV(逗号分隔值)是一种简单的文件格式,用于存储表格数据。每行代表一条记录,同一行中的不同字段用逗号分隔。
例如,一个记录水果店每日销售额的 CSV 文件内容可能如下:
日期,S1,S2,S3,S4,S5
5月21日,27.93,28.18,29.39,40.52,26.26
5月22日,58.08,50.61,51.62,48.55,54.03
5月23日,38.67,31.73,57.91,59.24,49.8- 第一行是标题行(表头),说明了每一列的含义。
- 后续每一行是一条数据记录。
- 字段之间用逗号
,分隔。
直接使用 open().readlines() 读取会得到字符串列表,需要手动用 split(',') 切分;csv 模块帮我们自动化了这些解析工作。
使用 csv.reader 读取
csv.reader 会返回一个读取器对象,迭代它即可逐行获取数据,每一行被自动解析为一个字符串列表。
为满足讲义代码可复制即跑、且不落盘写文件,下面的示例用内存流(io.StringIO)模拟 CSV 文件;真实项目中把 StringIO(...) 换成 open('文件路径', 'r') 即可。
import csv
from io import StringIO
# 模拟 CSV 文件内容(实际中来自 open('file.csv', 'r'))
csv_content = """日期,S1,S2,S3,S4,S5
5月21日,27.93,28.18,29.39,40.52,26.26
5月22日,58.08,50.61,51.62,48.55,54.03
5月23日,38.67,31.73,57.91,59.24,49.8"""
# 使用 StringIO 模拟文件对象(实际项目用 open('file.csv', 'r'))
reader = csv.reader(StringIO(csv_content))
rows = list(reader) # 转成列表,方便按索引访问
# 逐行打印,每一行都是一个列表
for row in rows:
print(row)
# rows[0] 是标题行,rows[1] 是5月21日,rows[2] 是5月22日
print("5月22日 S5 店销售额:{}".format(rows[2][5]))
print("5月23日 S5 店销售额:{}".format(rows[3][5]))['日期', 'S1', 'S2', 'S3', 'S4', 'S5']
['5月21日', '27.93', '28.18', '29.39', '40.52', '26.26']
['5月22日', '58.08', '50.61', '51.62', '48.55', '54.03']
['5月23日', '38.67', '31.73', '57.91', '59.24', '49.8']
5月22日 S5 店销售额:54.03
5月23日 S5 店销售额:49.8可以看到,每一行都变成了一个列表,每个字段是列表中的一个元素。所有值都是字符串类型,数字需要手动转换。
访问特定单元格:由于每行是列表,我们可以用下标访问。注意标题行占了索引 0,所以“5月22日”这一行是索引 2;列也同理,S1 到 S5 分别对应索引 1 到 5。想取 5月22日 S5 店的销售额,就写 rows[2][5],结果是 54.03。
注意:csv.reader 返回的是一个迭代器。如果想像上面这样通过下标随机访问,需要先用 list(reader) 把它整体转换成列表。
使用 csv.writer 写入
csv.writer 用于将数据写入 CSV 文件。你需要准备一个“列表的列表”(二维列表),外层列表的每个元素(内层列表)代表一行数据。
import csv
from io import StringIO
# 准备要写入的数据:一个二维列表
data = [
["姓名", "年龄"], # 标题行
["张三", 25],
["李四", 30],
["王五", 28]
]
# 创建一个 StringIO 对象模拟文件(实际项目用 open('file.csv', 'w', newline=''))
output = StringIO()
# 创建 writer 对象;内存流里统一用 \n 作行尾,真实文件则用 open(..., newline='') 避免多出空行
writer = csv.writer(output, lineterminator='\n')
# 逐行写入
for row in data:
writer.writerow(row)
# 获取写入的内容
content = output.getvalue()
print(content)姓名,年龄
张三,25
李四,30
王五,28关键点:
writerow()方法接受一个列表(或任何可迭代对象),将其作为一行写入。- 实际写入文件时,
open()应使用newline=''参数,以避免在 Windows 等系统上产生多余的空行。上面的示例写的是内存流,没有文件换行处理,因此用lineterminator='\n'让打印出来的行尾保持整齐。 - 写入时,数字等非字符串类型会被自动转换为字符串。
使用 csv.DictReader 读取(按列名访问)
csv.DictReader 会将 CSV 的每一行读成一个字典,字典的键来自标题行(第一行)的列名,值就是对应字段的值。这样可以通过列名来访问数据,比用下标更直观、不易出错。
import csv
from io import StringIO
csv_content = """日期,S1,S2,S3,S4,S5
5月21日,27.93,28.18,29.39,40.52,26.26
5月22日,58.08,50.61,51.62,48.55,54.03
5月23日,38.67,31.73,57.91,59.24,49.8"""
# 创建 DictReader 对象,并转成列表以便重复遍历
records = list(csv.DictReader(StringIO(csv_content)))
# 逐行读取,每行是一个字典
for row in records:
print(row)
# 通过列名 'S3' 访问,取出所有日期中 S3 店的销售额
s3_sales = [row["S3"] for row in records]
print("S3店在所有日期的销售额:{}".format(s3_sales)){'日期': '5月21日', 'S1': '27.93', 'S2': '28.18', 'S3': '29.39', 'S4': '40.52', 'S5': '26.26'}
{'日期': '5月22日', 'S1': '58.08', 'S2': '50.61', 'S3': '51.62', 'S4': '48.55', 'S5': '54.03'}
{'日期': '5月23日', 'S1': '38.67', 'S2': '31.73', 'S3': '57.91', 'S4': '59.24', 'S5': '49.8'}
S3店在所有日期的销售额:['29.39', '51.62', '57.91']注意:如果 CSV 文件没有标题行,可以在创建 DictReader 时通过 fieldnames 参数手动指定列名。
使用 csv.DictWriter 写入(按列名写入)
csv.DictWriter 需要你指定列名(fieldnames),然后写入一个字典的列表。每个字典的键应对应列名,值就是该行该列的数据。
import csv
from io import StringIO
# 准备数据:一个字典的列表
data = [
{"姓名": "张三", "年龄": 25},
{"姓名": "李四", "年龄": 30},
{"姓名": "王五", "年龄": 28}
]
# 定义列名(表头)
fieldnames = ["姓名", "年龄"]
output = StringIO()
# 创建 DictWriter 对象,指定列名(内存流里统一用 \n 作行尾)
dict_writer = csv.DictWriter(output, fieldnames=fieldnames, lineterminator='\n')
# 先写入标题行
dict_writer.writeheader()
# 写入所有数据行
dict_writer.writerows(data) # 注意是 writerows(复数),一次写入多行
content = output.getvalue()
print(content)姓名,年龄
张三,25
李四,30
王五,28关键点:
fieldnames参数定义了 CSV 文件的列顺序和标题行内容。writeheader()方法写入标题行。writerow(row_dict)写入单行字典,writerows(list_of_dicts)一次写入多行。- 数据字典中的键必须与
fieldnames中定义的列名一致;若出现未声明的额外键会抛出ValueError。
📝 动手练一练
假设你有一份学生成绩单的 CSV 数据,内容如下:
姓名,语文,数学,英语
小明,85,90,88
小红,92,89,93
小刚,78,85,80请使用 csv.DictReader 读取数据,并计算每位学生的平均分(保留两位小数),然后使用 csv.DictWriter 将结果(包含“姓名”和“平均分”两列)写入一个新的 CSV 字符串。
👉 点击查看参考答案
思路:先用 DictReader 读取,遍历每一行(字典),计算三门课的平均值。将结果存入一个新列表,每个元素是包含“姓名”和“平均分”的字典。最后用 DictWriter 写入。
import csv
from io import StringIO
# 原始数据
original_data = """姓名,语文,数学,英语
小明,85,90,88
小红,92,89,93
小刚,78,85,80"""
# 1. 读取并计算平均分
input_io = StringIO(original_data)
reader = csv.DictReader(input_io)
students_with_avg = []
for row in reader:
name = row['姓名']
# 将成绩转换为整数进行计算
chinese = int(row['语文'])
math = int(row['数学'])
english = int(row['英语'])
average = (chinese + math + english) / 3
# 保留两位小数
average_rounded = round(average, 2)
students_with_avg.append({"姓名": name, "平均分": average_rounded})
# 2. 将结果写入新的 CSV 字符串
output_io = StringIO()
fieldnames = ["姓名", "平均分"]
writer = csv.DictWriter(output_io, fieldnames=fieldnames, lineterminator='\n')
writer.writeheader()
writer.writerows(students_with_avg)
result = output_io.getvalue()
print(result)姓名,平均分
小明,87.67
小红,91.33
小刚,81.0常见错误
| 错误写法 | 现象或报错 | 原因 | 正确写法 |
|---|---|---|---|
写文件时未指定 newline='' | 在 Windows 系统生成的 CSV 文件行间可能出现空行 | Windows 下 \n 会被转换为 \r\n,csv 模块自己也会添加换行符,导致双重换行。 | with open('file.csv', 'w', newline='', encoding='utf-8') as f: |
用数字下标访问 DictReader 的某一行 | TypeError: 'DictReader' object is not subscriptable | DictReader 对象是迭代器,不支持下标直接访问。需要先转换为列表。 | rows = list(dict_reader)target_row = rows[1] |
使用 DictWriter 时,数据字典的键多于 fieldnames | ValueError: dict contains fields not in fieldnames | DictWriter 默认只允许写入 fieldnames 中声明的列,出现额外键会直接报错。 | 确保数据字典的键与 fieldnames 一致。 |
认为 csv.reader 返回的数据是数字类型 | 计算时得到字符串拼接或类型错误 | csv 模块将所有字段读作字符串。需要数字时需手动转换。 | float(row[1]), int(row[2]) |
小结
- CSV 是一种逗号分隔的纯文本表格格式,易于生成和解析,是常见的数据交换格式。
csv.reader用于读取:将 CSV 每行解析为字符串列表,可通过数字下标访问字段。csv.writer用于写入:接受一个“列表的列表”,用writerow()或writerows()写入文件。csv.DictReader更便于按列名访问:将每行解析为字典,键为标题行的列名,访问更直观。csv.DictWriter按列名写入:需指定fieldnames,先writeheader()写标题,再写入字典列表。- 所有读取的字段默认都是字符串类型,进行数值计算前需要手动转换(如
int(),float())。
📋 行动清单
- 用
csv.reader读取一个简单的 CSV 字符串,并练习通过下标访问特定的行和列。 - 用
csv.DictReader读取同一个 CSV 字符串,改用列名访问数据,体会其便利性。 - 创建一个包含你几位朋友姓名和年龄的字典列表,并用
csv.DictWriter将其写入到一个字符串中,查看生成的结果。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载数据集:fruit-sold.csv(水果店每日销售额,与 CSV 节示例一致)CSV · 141B