PEP 305 – CSV 文件 API
- 作者:
- Kevin Altis <altis at semi-retired.com>,Dave Cole <djc at object-craft.com.au>,Andrew McNamara <andrewm at object-craft.com.au>,Skip Montanaro <skip at pobox.com>,Cliff Wells <LogiplexSoftware at earthlink.net>
- 讨论列表:
- Csv 列表
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2003年1月26日
- Python 版本:
- 2.3
- 历史记录:
- 2003年1月31日,2003年2月13日
摘要
逗号分隔值 (CSV) 文件格式是电子表格和数据库最常见的导入和导出格式。尽管许多 CSV 文件都很容易解析,但该格式没有由稳定的规范正式定义,并且足够微妙,以至于使用类似 line.split(",")
的方法来解析 CSV 文件的行最终注定会失败。本 PEP 定义了读取和写入 CSV 文件的 API。它附带了一个实现该 API 的相应模块。
待办事项(感兴趣和有抱负者的说明)
- 对选择将文件对象传递给构造函数的动机进行了更好的阐述。参见 https://mail.python.org/pipermail/csv/2003-January/000179.html
- Unicode。哎。
应用领域
本 PEP 的重点是做好一件事:解析可能使用各种字段分隔符、引用字符、引用转义机制和换行符的表格数据。作者希望提出的模块能够有效地解决此解析问题。作者不打算解决以下任何相关主题
- 数据解释(包含字符串“10”的字段应该是一个字符串、浮点数还是整数?它是 10 进制、16 进制还是 2 进制数?带引号的数字是数字还是字符串?)
- 特定于区域设置的数据表示(数字 1.23 应该写成“1.23”还是“1,23”或“1 23”?)——这可能会在将来得到解决。
- 固定宽度表格数据 - 已经可以可靠地解析。
基本原理
通常,CSV 文件的格式足够简单,您可以逐行读取它们并在分隔字段的逗号处进行分割。如果所有读取的数据都是数字,则尤其如此。这种方法可能在一段时间内有效,然后当有人在数据中放入一些意外的东西(例如逗号)时,就会反过来咬你一口。当您深入研究问题时,您最终可能会得出结论,可以使用正则表达式来解决问题。这将在一段时间内有效,然后有一天会神秘地中断。问题越来越严重,因此您深入研究,最终意识到您需要一个针对该格式的专用解析器。
CSV 格式没有明确定义,并且不同的实现存在许多细微的极端情况。有人建议首字母缩写词中的“V”代表“模糊”(Vague)而不是“值”(Values)。不同的分隔符和引用字符仅仅是开始。某些程序在每个分隔符之后生成空格,该空格不属于后续字段。其他程序通过加倍嵌入的引用字符来引用它们,其他程序则通过在它们前面加上转义字符来引用它们。奇怪做法的列表似乎无穷无尽。
所有这些可变性意味着程序员难以可靠地解析来自许多来源的 CSV 文件或生成旨在馈送到特定外部程序的 CSV 文件,而无需充分了解这些来源和程序。本 PEP 及其附带的软件试图使该过程不那么脆弱。
现有模块
这个问题以前也已被解决过。目前 Python 社区中至少有三个模块使程序员能够读取和写入 CSV 文件
每个模块都有不同的 API,这使得程序员在它们之间切换变得有些困难。更大的问题可能是它们对某些 CSV 极端情况的解释方式不同,因此即使克服了不同模块 API 之间的差异,程序员也必须处理软件包之间的语义差异。
模块接口
本 PEP 支持三个基本 API,一个用于读取和解析 CSV 文件,一个用于写入它们,以及一个用于识别读取器和写入器对不同 CSV 方言的 API。
读取 CSV 文件
CSV 读取器由读取器工厂函数创建
obj = reader(iterable [, dialect='excel']
[optional keyword args])
读取器对象是一个迭代器,它将返回行的可迭代对象作为唯一必需的参数。如果它支持二进制模式(文件对象确实如此),则传递给读取器函数的可迭代参数必须以二进制模式打开。这使得读取器对象可以完全控制对文件内容的解释。可选的 dialect 参数将在下面讨论。读取器函数还接受几个可选的关键字参数,这些参数定义了解析器的特定格式设置(请参阅“格式化参数”部分)。读取器通常按如下方式使用
csvreader = csv.reader(file("some.csv"))
for row in csvreader:
process(row)
读取器对象返回的每一行都是字符串或 Unicode 对象的列表。
当同时将 dialect 参数和各个格式化参数传递给构造函数时,首先查询 dialect 的格式化参数,然后检查各个格式化参数。
写入 CSV 文件
创建写入器类似
obj = writer(fileobj [, dialect='excel'],
[optional keyword args])
写入器对象是围绕以二进制模式打开以进行写入的文件类对象的包装器(如果存在此类区别)。它接受与读取器构造函数相同的可选关键字参数。
写入器通常按如下方式使用
csvwriter = csv.writer(file("some.csv", "w"))
for row in someiterable:
csvwriter.writerow(row)
要生成一组字段名称作为 CSV 文件的第一行,程序员必须显式地写入它,例如
csvwriter = csv.writer(file("some.csv", "w"), fieldnames=names)
csvwriter.write(names)
for row in someiterable:
csvwriter.write(row)
或安排它成为正在写入的可迭代对象的第一行。
管理不同的方言
由于 CSV 是一种定义不明确的格式,因此一个 CSV 文件可以有多种方式与另一个 CSV 文件不同,但包含完全相同的数据。许多可以导入或导出表格数据的工具允许用户指示字段分隔符、引用字符、换行符以及文件的其他特征。这些特征相当容易确定,但仍然有点麻烦需要弄清楚,并且在单独指定时会导致相当长的函数调用。
为了尽量减少确定和指定大量格式化参数的难度,读取器和写入器对象支持一个 dialect 参数,它只是一个方便的句柄,用于处理这些较低级别参数的组。当 dialect 作为字符串给出时,它通过其注册函数标识模块已知的方言之一,否则它必须是 Dialect 类的实例,如下所述。
方言通常以定义特定格式约束集的应用程序或组织命名。在撰写本文时,模块中定义了两种方言,“excel”,它描述了 Excel 97 和 Excel 2000 导出 CSV 文件的默认格式约束,以及“excel-tab”,它与“excel”相同,但指定 ASCII 制表符作为字段分隔符。
方言被实现为仅属性类,以使用户能够通过子类化构建变体方言。“excel”方言是 Dialect 的子类,定义如下
class Dialect:
# placeholders
delimiter = None
quotechar = None
escapechar = None
doublequote = None
skipinitialspace = None
lineterminator = None
quoting = None
class excel(Dialect):
delimiter = ','
quotechar = '"'
doublequote = True
skipinitialspace = False
lineterminator = '\r\n'
quoting = QUOTE_MINIMAL
“excel-tab”方言定义如下
class exceltsv(excel):
delimiter = '\t'
(有关各个格式化参数的说明,请参阅“格式化参数”部分。)
为了支持对特定方言的字符串引用,模块定义了几个函数
dialect = get_dialect(name)
names = list_dialects()
register_dialect(name, dialect)
unregister_dialect(name)
get_dialect()
返回与给定名称关联的方言实例。list_dialects()
返回所有已注册方言名称的列表。register_dialects()
将字符串名称与方言类关联。unregister_dialect()
删除名称/方言关联。
格式化参数
除了 dialect 参数之外,读取器和写入器构造函数都采用几个特定的格式化参数,这些参数指定为关键字参数。理解的格式化参数为
quotechar
指定用作引用字符的单字符字符串。默认为“”。将其设置为 None 与将 quoting 设置为 csv.QUOTE_NONE 的效果相同。delimiter
指定用作字段分隔符的单字符字符串。默认为“,”。escapechar
指定一个单字符字符串,用于在 quotechar 设置为 None 时转义分隔符。skipinitialspace
指定如何解释紧跟在分隔符后面的空格。默认为 False,这意味着紧跟在分隔符后面的空格是后续字段的一部分。lineterminator
指定应终止行的字符序列。quoting
控制何时应由写入器生成引号。它可以采用以下任何模块常量- csv.QUOTE_MINIMAL 表示仅在需要时,例如,当字段包含 quotechar 或 delimiter 时
- csv.QUOTE_ALL 表示始终在字段周围放置引号。
- csv.QUOTE_NONNUMERIC 表示始终在非数字字段周围放置引号。
- csv.QUOTE_NONE 表示从不在字段周围放置引号。
doublequote
控制字段内引号的处理。当为 True 时,在读取过程中将两个连续的引号解释为一个引号,在写入过程中,每个引号都写成两个引号。
在处理 dialect 设置和一个或多个其他可选参数时,会在处理各个格式化参数之前处理 dialect 参数。这使得很容易选择一个方言,然后覆盖一个或多个设置,而无需定义新的方言类。例如,如果 CSV 文件是由 Excel 2000 使用单引号作为引用字符和冒号作为分隔符生成的,则可以创建如下读取器
csvreader = csv.reader(file("some.csv"), dialect="excel",
quotechar="'", delimiter=':')
由于对“excel”方言的引用,Excel 生成 CSV 文件的其他细节将自动处理。
读取器对象
读取器对象是可迭代对象,其 next() 方法返回一系列字符串,每行一个字段一个字符串。
写入器对象
写入器对象有两种方法,writerow() 和 writerows()。前者接受一个要写入输出的可迭代对象(通常是列表)字段。后者接受一个可迭代对象列表,并为每个可迭代对象调用 writerow()。
实现
有一个示例实现可用。 [1] 目标是有效地实现 PEP 中描述的 API。它很大程度上基于 Object Craft csv 模块。 [2]
测试
示例实现 [1] 包括一组测试用例。
问题
- 参数是否应该控制如何解释连续的分隔符?我们的想法是“不”。连续的分隔符应该始终表示一个空字段。
- Unicode 怎么样?将从 codecs.open() 获取的文件对象传递进去是否足够?例如
csvreader = csv.reader(codecs.open("some.csv", "r", "cp1252")) csvwriter = csv.writer(codecs.open("some.csv", "w", "utf-8"))
在第一个示例中,将假设文本的编码为 cp1252。系统是否应该积极地转换为 Unicode,或者是否仅在必要时返回 Unicode 字符串?
在第二个示例中,文件会在写入磁盘之前自动将 Unicode 字符串编码为 utf-8。
注意:截至撰写本文时,csv 模块尚不支持 Unicode 数据。
- 其他转义约定如何处理?如果使用的方言包含一个
escapechar
参数且该参数不为 None,并且quoting
参数设置为 QUOTE_NONE,则字段中出现的定界符在写入时将在其前面加上转义字符,并且在读取时也需要在其前面加上转义字符。 - 是否应该有一个“完全引用”的写入模式?“除了数值之外完全引用”又如何呢?这两种模式都已实现(分别为 QUOTE_ALL 和 QUOTE_NONNUMERIC)。
- 换行符如何处理?如果我在 Unix 系统上生成一个 CSV 文件,Excel 是否能够正确识别仅包含 LF 的行终止符?文件必须使用二进制模式以适当的方式打开以进行读取或写入。将
lineterminator
序列指定为'\r\n'
。生成的 文件将被正确写入。 - 是否可以提供一个选项,以便从 reader 生成字典,并由 writer 接受字典?请参阅 csv.py 中的 DictReader 和 DictWriter 类。
- 引用字符和定界符是否仅限于单个字符?目前是的。
- 如何处理不同长度的行?数据的解释是应用程序的工作。在此级别上,不存在“短行”或“长行”的概念。
参考文献
网络上有很多关于其他与 CSV 相关的项目的参考。这里列出了一些。
版权
本文档已置于公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0305.rst
上次修改时间:2023-09-09 17:39:29 GMT