本文详细介绍了如何使用Python的csv模块将一个大型CSV文件中的每一行数据拆分并写入到单独的CSV文件中。核心内容包括利用csv.writer正确处理CSV格式,以及通过contextlib.ExitStack和字典管理多个文件写入器,以高效、健壮地解决文件名冲突和资源管理问题,确保数据准确无误地分散到指定的新文件中。
1. CSV数据拆分与写入基础
在Python中处理CSV文件时,如果需要将原始文件中的每一行或特定行的数据提取出来,并保存到以行内容命名的独立CSV文件中,一个常见的需求是将特定字段作为新文件的文件名,并将其他字段写入到这个新文件中。
假设我们有一个包含Order Number、Date和File Name三列的CSV文件,目标是为每一行创建一个新的CSV文件,文件名取自File Name字段,新文件中只包含Order Number和Date字段,且不带表头。
最初尝试可能直接使用f.write()方法将字段内容写入文件。然而,这种方法存在一个核心问题:f.write()仅仅是写入字符串,它不会自动添加CSV文件所需的字段分隔符(如逗号)。这会导致所有字段内容被连接成一个单一的字符串,而不是以逗号分隔的多个字段。
错误示例(仅供理解问题,不建议使用):
立即学习“Python免费学习笔记(深入)”;
import csv # 假设TestExport.csv存在于指定路径 # with open("//server2/shared/Data/TestExport.csv",'r') as csvfile: # reader = csv.DictReader(csvfile) # for row in reader: # file_name ='{0}.csv'.format(row['FileName']) # with open(file_name, 'w') as f: # f.write(row['Order Number']) # f.write(row['Date'])
上述代码的问题在于f.write(row[‘Order Number’])和f.write(row[‘Date’])会将两个字符串直接连接起来,例如123452023-01-01,而不是12345,2023-01-01。
2. 使用csv.writer正确写入CSV数据
解决上述问题的关键在于使用Python内置csv模块提供的csv.writer对象。csv.writer专门用于处理CSV格式的写入,它能够自动处理字段分隔符和行结束符。
核心改进点:
- csv.writer(out_f, delimiter=’,’): 创建一个CSV写入器,并指定逗号作为字段分隔符。
- writer.writerow([…]): 使用此方法写入一行数据。它接受一个列表作为参数,列表中的每个元素将作为一个字段写入,并自动添加分隔符。
- newline=”: 在打开文件时,对于csv.writer,必须指定newline=”。这是因为csv模块会自行处理换行符,如果Python的默认换行转换机制也介入,可能会导致文件中出现双重换行,或在不同操作系统间产生兼容性问题。
正确实现示例:
import csv # 假设TestExport.csv是你的源文件 source_csv_path = "//server2/shared/Data/TestExport.csv" with open(source_csv_path, 'r', encoding='utf-8') as in_f: # 建议指定编码 reader = csv.DictReader(in_f) for row in reader: # 根据'FileName'字段生成新CSV的文件名 file_name = '{0}.csv'.format(row['FileName']) # 以写入模式打开新文件,并指定newline='' with open(file_name, 'w', newline='', encoding='utf-8') as out_f: # 建议指定编码 # 创建csv写入器,指定逗号为分隔符 writer = csv.writer(out_f, delimiter=',') # 写入Order Number和Date字段。writerow接受一个列表 writer.writerow([row['Order Number'], row['Date']]) print("所有行已成功拆分并写入独立CSV文件。")
这段代码能够正确地将每一行数据拆分并写入到各自的CSV文件中,每个新文件只包含Order Number和Date两个字段,并以逗号分隔。
3. 处理文件名冲突与资源管理:使用contextlib.ExitStack
上述解决方案虽然正确,但存在一个潜在问题:如果源CSV文件中有两行或多行具有相同的File Name字段值,那么后一行的数据将覆盖前一行的数据,因为每次循环都会重新打开并清空同名文件。此外,频繁地打开和关闭文件也可能影响性能。
为了解决这个问题,我们可以采用更高级的策略:
- 复用写入器: 使用一个字典来存储已经创建的csv.writer对象。当遇到一个已存在文件名的行时,直接使用字典中对应的写入器追加数据,而不是重新创建文件。
- 统一文件管理: 由于我们会打开多个文件并保持它们处于打开状态以供复用,我们需要一种机制来确保所有这些文件最终都能被正确关闭。contextlib.ExitStack是处理这种情况的理想工具。它允许你在一个with语句块中管理多个上下文管理器(如文件对象),并在with块结束时自动关闭所有被管理的资源。
健壮的解决方案示例:
import csv import contextlib source_csv_path = "//server2/shared/Data/TestExport.csv" with open(source_csv_path, 'r', encoding='utf-8') as in_f: # writers字典用于存储每个文件对应的csv.writer对象 # 键是文件名,值是对应的csv.writer实例 writers = {} # 使用ExitStack来管理所有打开的文件对象 with contextlib.ExitStack() as stack: reader = csv.DictReader(in_f) for row in reader: file_name = '{0}.csv'.format(row['FileName']) # 尝试从writers字典中获取当前文件名的写入器 writer = writers.get(file_name) # 如果该文件名的写入器尚未创建 if writer is None: # 使用stack.enter_context()打开新文件。 # ExitStack会负责在with块结束时关闭此文件。 out_f = stack.enter_context(open(file_name, 'w', newline='', encoding='utf-8')) # 创建新的csv写入器并存储到writers字典中 writer = csv.writer(out_f) writers[file_name] = writer # (可选)为新创建的文件写入表头 # 如果不需要表头,可以删除下面这行 writer.writerow(['OrderNumber', 'Date']) # 使用获取到的(或新创建的)写入器写入数据行 writer.writerow([row['Order Number'], row['Date']]) print("所有行已成功拆分并写入独立CSV文件,重复文件名的数据已追加。")
4. 代码详解与注意事项
- contextlib.ExitStack: 这是一个强大的上下文管理器。通过stack.enter_context(resource),你可以将任何上下文管理器(如open()返回的文件对象)注册到ExitStack中。当最外层的with contextlib.ExitStack() as stack:块结束时,无论以何种方式(正常退出、异常),ExitStack都会确保所有注册的资源按LIFO(后进先出)顺序被正确关闭。这避免了手动管理多个文件句柄的复杂性。
- writers 字典: 这个字典是实现写入器复用的关键。它将文件名映射到对应的csv.writer对象。当处理一行数据时,程序首先检查writers中是否已有该文件名的写入器。如果没有,就创建一个新的文件和写入器,并将其添加到字典中;如果已经存在,则直接使用已有的写入器进行写入。
- 可选的表头写入: 在if writer is None:块中,我们可以在文件首次被创建时写入一个表头(writer.writerow([‘OrderNumber’, ‘Date’]))。这确保了每个新生成的CSV文件都有一个清晰的表头,并且表头只会被写入一次。如果不需要表头,可以删除这行代码。
- 编码(encoding=’utf-8’): 在打开文件时,显式指定编码是一个好习惯,尤其是处理包含非ASCII字符的数据时,utf-8是推荐的通用编码。
- 性能: 相比于每次循环都打开和关闭文件,使用ExitStack和写入器复用可以显著提高处理大量数据时的性能,因为它减少了文件I/O操作的开销。
总结
通过本教程,我们学习了如何使用Python的csv模块将一个CSV文件按行拆分为多个独立的CSV文件。从基础的csv.writer使用到更高级的contextlib.ExitStack和字典组合,我们解决了文件名冲突和资源管理问题,确保了数据拆分过程的准确性、健壮性和高效性。掌握这些技术将帮助你在处理CSV数据时更加灵活和专业。
python 操作系统 编码 工具 csv csv文件 red Python Resource if date 字符串 循环 number 对象 ASCII