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”的字段应该是字符串、浮点数还是整数?它是十进制、十六进制还是二进制数?引号中的数字是数字还是字符串?)
- 特定于语言环境的数据表示(数字 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 方言。
读取 CSV 文件
CSV 读取器使用 reader 工厂函数创建
obj = reader(iterable [, dialect='excel']
[optional keyword args])
读取器对象是一个迭代器,它将返回行的可迭代对象作为唯一的必需参数。如果它支持二进制模式(文件对象支持),则传递给 reader 函数的可迭代参数必须已在二进制模式下打开。这使读取器对象能够完全控制文件内容的解释。可选的 dialect 参数在下面讨论。reader 函数还接受几个可选的关键字参数,这些参数定义解析器的特定格式设置(请参阅“格式化参数”部分)。读取器通常按如下方式使用
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 类的实例。
方言通常以定义特定格式约束集的应用程序或组织命名。截至本文撰写之时,模块中定义了两种方言:“excel”,它描述了 Excel 97 和 Excel 2000 导出 CSV 文件的默认格式约束,以及“excel-tab”,它与“excel”相同,但指定 ASCII TAB 字符作为字段分隔符。
方言实现为仅属性类,以使用户能够通过子类化构造变体方言。“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 时,在读取期间两个连续的引号被解释为一个,在写入时,每个引号被写入为两个引号。
在处理方言设置和一个或多个其他可选参数时,方言参数在单独的格式参数之前处理。这使得选择方言然后覆盖一个或多个设置而无需定义新的方言类变得容易。例如,如果 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 数据。
- 其他转义约定呢?如果正在使用的方言包含一个非 None 的
escapechar
参数,并且quoting
参数设置为 QUOTE_NONE,则字段中出现的分隔符在写入时将以转义字符为前缀,并且在读取时也应以转义字符为前缀。 - 写入是否应该有一个“完全引用”模式?“除了数值之外完全引用”呢?两者都已实现(分别为 QUOTE_ALL 和 QUOTE_NONNUMERIC)。
- 行尾呢?如果我在 Unix 系统上生成一个 CSV 文件,Excel 能否正确识别仅 LF 的行终止符?文件必须以二进制模式打开以进行适当的读取或写入。将
lineterminator
序列指定为'\r\n'
。生成的将正确写入文件。 - 是否有一个选项可以从读取器生成字典并由写入器接受字典?请参阅 csv.py 中的 DictReader 和 DictWriter 类。
- 引用字符和分隔符是否仅限于单个字符?目前是这样。
- 如何处理不同长度的行?数据的解释是应用程序的工作。在此级别上没有“短行”或“长行”之说。
参考资料
网络上有很多关于其他 CSV 相关项目的参考。这里包含一些。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0305.rst