查看全课大纲(第 11 / 51 节)
  1. 1.Python 是什么?为什么值得学?
  2. 2.如何为 Python 编程准备一个“全家桶”环境?
  3. 3.Python 有哪几种执行方法?
  4. 4.行与缩进到底有什么用?
  5. 5.注释有什么用,怎么写?
  6. 6.变量:如何给数据起个好名字?
  7. 7.运算符的优先级和常见坑点是什么?
  8. 8.Python 中,数字和布尔值究竟有哪几种?
  9. 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
  10. 10.如何将程序的结果展示给用户或保存下来?
  11. 11.CSV 文件怎么读怎么写才方便?
  12. 12.列表是什么,如何创建和访问它?
  13. 13.列表的修改操作,有哪些方法?
  14. 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
  15. 15.判断语句:如何让程序根据条件做不同的事?
  16. 16.for 循环如何遍历序列中的元素?
  17. 17.while 循环什么时候才会停?
  18. 18.while 循环的结束条件,你写对了吗?
  19. 19.循环中途想提前结束,怎么办?
  20. 20.`break` 和 `continue` 到底有什么区别?
  21. 21.列表推导式如何让代码更简洁高效?
  22. 22.字符串怎么定义、拼接和操作?
  23. 23.元组:为什么定义单个元素时要加逗号?
  24. 24.集合(set)怎么用?如何快速去重和做集合运算?
  25. 25.字典是什么,为什么比列表更方便?
  26. 26.字典的增删改查与遍历,如何避免 KeyError?
  27. 27.函数里的变量,为什么有的能用有的不能用?
  28. 28.在函数内部修改全局变量,为什么有时不生效?
  29. 29.函数里能修改全局变量吗?返回值怎么用?
  30. 30.匿名函数:如何用 lambda 快速定义简单函数?
  31. 31.面向对象编程是什么,为什么需要它?
  32. 32.类中的方法和属性到底怎么定义和使用?
  33. 33.如何控制对对象内部属性和方法的访问?
  34. 34.异常发生时,程序会怎样继续执行?
  35. 35.如何组织代码:包和模块
  36. 36.`if __name__ == '__main__'` 到底有什么用?
  37. 37.如何用 IDE 的 Debug 功能排查程序错误?
  38. 38.Matplotlib 绘图的第一步该做什么?
  39. 39.Figure 是什么,如何设置图形尺寸?
  40. 40.如何自定义 Matplotlib 的坐标轴?
  41. 41.如何为图表添加图例,让不同曲线一目了然?
  42. 42.饼图怎么画才能直观展示数据占比?
  43. 43.如何用 Matplotlib 绘制散点图?
  44. 44.柱状图怎么画?
  45. 45.如何使用 Matplotlib 绘制等高线图?
  46. 46.如何在 Matplotlib 中绘制子图?
  47. 47.zip 函数如何把多个列表“打包”在一起?
  48. 48.如何给可迭代对象加上索引?
  49. 49.random 模块如何生成随机数?
  50. 50.math 模块怎么用?
  51. 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

关键点:

  1. writerow() 方法接受一个列表(或任何可迭代对象),将其作为一行写入。
  2. 实际写入文件时,open() 应使用 newline='' 参数,以避免在 Windows 等系统上产生多余的空行。上面的示例写的是内存流,没有文件换行处理,因此用 lineterminator='\n' 让打印出来的行尾保持整齐。
  3. 写入时,数字等非字符串类型会被自动转换为字符串。

使用 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

关键点:

  1. fieldnames 参数定义了 CSV 文件的列顺序和标题行内容。
  2. writeheader() 方法写入标题行。
  3. writerow(row_dict) 写入单行字典,writerows(list_of_dicts) 一次写入多行。
  4. 数据字典中的键必须与 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 subscriptableDictReader 对象是迭代器,不支持下标直接访问。需要先转换为列表。rows = list(dict_reader)
target_row = rows[1]
使用 DictWriter 时,数据字典的键多于 fieldnamesValueError: dict contains fields not in fieldnamesDictWriter 默认只允许写入 fieldnames 中声明的列,出现额外键会直接报错。确保数据字典的键与 fieldnames 一致。
认为 csv.reader 返回的数据是数字类型计算时得到字符串拼接或类型错误csv 模块将所有字段读作字符串。需要数字时需手动转换。float(row[1]), int(row[2])

小结

  1. CSV 是一种逗号分隔的纯文本表格格式,易于生成和解析,是常见的数据交换格式。
  2. csv.reader 用于读取:将 CSV 每行解析为字符串列表,可通过数字下标访问字段。
  3. csv.writer 用于写入:接受一个“列表的列表”,用 writerow() 或 writerows() 写入文件。
  4. csv.DictReader 更便于按列名访问:将每行解析为字典,键为标题行的列名,访问更直观。
  5. csv.DictWriter 按列名写入:需指定 fieldnames,先 writeheader() 写标题,再写入字典列表。
  6. 所有读取的字段默认都是字符串类型,进行数值计算前需要手动转换(如 int(), float())。

📋 行动清单

  • 用 csv.reader 读取一个简单的 CSV 字符串,并练习通过下标访问特定的行和列。
  • 用 csv.DictReader 读取同一个 CSV 字符串,改用列名访问数据,体会其便利性。
  • 创建一个包含你几位朋友姓名和年龄的字典列表,并用 csv.DictWriter 将其写入到一个字符串中,查看生成的结果。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 数据集:fruit-sold.csv(水果店每日销售额,与 CSV 节示例一致)
    CSV · 141B
    下载