"csv" --- CSV 文件读写
**********************

**源代码：** Lib/csv.py

======================================================================

CSV (Comma Separated Values) 格式是电子表格和数据库中最常见的输入、输
出文件格式。在 **RFC 4180** 规范推出的很多年前，CSV 格式就已经被开始使
用了，由于当时并没有合理的标准，不同应用程序读写的数据会存在细微的差别
。这种差别让处理多个来源的 CSV 文件变得困难。但尽管分隔符会变化，此类
文件的大致格式是相似的，所以编写一个单独的模块以高效处理此类数据，将程
序员从读写数据的繁琐细节中解放出来是有可能的。

"csv" 模块实现了 CSV 格式表单数据的读写。其提供了诸如“以兼容 Excel 的
方式输出数据文件”或“读取 Excel 程序输出的数据文件”的功能，程序员无需知
道 Excel 所采用 CSV 格式的细节。此模块同样可以用于定义其他应用程序可用
的 CSV 格式或定义特定需求的 CSV 格式。

"csv" 模块中的 "reader" 类和 "writer" 类可用于读写序列化的数据。也可使
用 "DictReader" 类和 "DictWriter" 类以字典的形式读写数据。

参见:

  该实现在“Python 增强提议” - PEP *305* (CSV 文件 API)  中被提出
     《Python 增强提议》提出了对 Python 的这一补充。


模块内容
========

"csv" 模块定义了以下函数：

csv.reader(csvfile, /, dialect='excel', **fmtparams)

   返回一个 reader 对象，该对象将处理给定 *csvfile* 中的行。 csvfile
   必须是一个包含字符串的可迭代对象，使用 reader 所定义的 csv 格式。
   csvfile 通常是一个文件型对象或列表。 如果 *csvfile* 是一个文件对象
   ，则打开它时应设置 "newline=''". [1]  给定可选 *dialect* 形参将被用
   于定义一组专属于特定 CSV 变种的形参。 它可以是 "Dialect" 类的子类的
   实例，或是 "list_dialects()" 函数所返回的字符串之一。 另一个可选关
   键字形参 *fmtparams* 可被用来覆盖当前变种中的单个格式形参。 有关变
   种和格式设置形参的完整细节，请参阅 变种与格式参数 一节。

   Each row read from the从 csv 文件读取的每一行都将返回为一个字符串列
   表。 除非指定了 "QUOTE_NONNUMERIC" 格式选项（在这种情况下未加引号的
   字段会被转换为浮点数）否则不会执行自动数据类型转换。

   一个简短的用法示例:

      >>> import csv
      >>> with open('eggs.csv', newline='') as csvfile:
      ...     spamreader = csv.reader(csvfile, delimiter=' ', quotechar='|')
      ...     for row in spamreader:
      ...         print(', '.join(row))
      Spam, Spam, Spam, Spam, Spam, Baked Beans
      Spam, Lovely Spam, Wonderful Spam

csv.writer(csvfile, /, dialect='excel', **fmtparams)

   返回一个 writer 对象，该对象负责将用户的数据在给定的文件型对象上转
   换为带分隔符的字符串。 *csvfile* 可以是任何具有 "write()" 方法的对
   象。 如果 *csvfile* 是一个文件对象，则打开它时应使用 "newline=''"
   [1]。 可以给出可选的 *dialect* 形参用来定义一组特定 CSV 变种专属的
   形参。 它可以是 "Dialect" 类的某个子类的实例或是 "list_dialects()"
   函数所返回的字符串之一。 还可以给出另一个可选的 *fmtparams* 关键字
   参数来覆盖当前变种中的单个格式化形参。 有关各个变种和格式化形参的完
   整细节，请参阅 变种与格式参数 部分。 为了尽量简化与实现 DB API 的模
   块之间的接口，可以将 "None" 作为空字符串写入。 虽然这个转换是不可逆
   的，但它可以简化 SQL NULL 数据值到 CSV 文件的转储而无需预处理从
   "cursor.fetch*" 调用返回的数据。 在被写入之前所有其他非字符串数据都
   会先用 "str()" 来转换为字符串。

   一个简短的用法示例:

      import csv
      with open('eggs.csv', 'w', newline='') as csvfile:
          spamwriter = csv.writer(csvfile, delimiter=' ',
                                  quotechar='|', quoting=csv.QUOTE_MINIMAL)
          spamwriter.writerow(['Spam'] * 5 + ['Baked Beans'])
          spamwriter.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam'])

csv.register_dialect(name, /, dialect='excel', **fmtparams)

   将 *dialect* 与 *name* 关联起来。  *name* 必须是字符串。 变种的指定
   可以通过传入一个 "Dialect" 的子类，或通过 *fmtparams* 关键字参数，
   或是两者同时传入，此时关键字参数会覆盖 dialect 形参。 有关变种和格
   式化形参的完整细节，请参阅 变种与格式参数 部分。

csv.unregister_dialect(name)

   从变种注册表中删除 *name* 对应的变种。如果 *name* 不是已注册的变种
   名称，则抛出 "Error" 异常。

csv.get_dialect(name)

   返回 *name* 对应的变种。如果 *name* 不是已注册的变种名称，则抛出
   "Error" 异常。该函数返回的是不可变的 "Dialect" 对象。

csv.list_dialects()

   返回所有已注册变种的名称。

csv.field_size_limit()
csv.field_size_limit(new_limit)

   返回解析器当前允许的最大字段大小。如果指定了 *new_limit*，则它将成
   为新的最大字段大小。

"csv" 模块定义了以下类：

class csv.DictReader(f, fieldnames=None, restkey=None, restval=None, dialect='excel', *args, **kwds)

   创建一个对象，该对象在操作上类似于常规 reader，但是将每行中的信息映
   射到一个 "dict"，该 dict 的键由 *fieldnames* 可选参数给出。

   *fieldnames* 形参是一个 *sequence*。 如果省略 *fieldnames*，则文件
   *f* 第一行中的值将用作字段名并将从结果中去除。 如果提供了
   *fieldnames*，它们将被使用而第一行将包括在结果中。 无论字段名是如何
   确定的，字典都将保留其原始顺序。

   如果某一行中的字段多于字段名，则剩余数据会被放入一个列表，并与
   *restkey* 所指定的字段名 (默认为 "None") 一起保存。 如果某个非空白
   行的字段少于字段名，则缺失的值会使用 *restval* 的值来填充 (默认为
   "None")。

   所有其他可选或关键字参数都传递给底层的 "reader" 实例。

   如果传给 *fieldnames* 的参数是一个迭代器，它将被强制转换为 "list"。

   在 3.6 版本发生变更: 返回的行现在的类型是 "OrderedDict"。

   在 3.8 版本发生变更: 现在，返回的行是 "dict" 类型。

   一个简短的用法示例:

      >>> import csv
      >>> with open('names.csv', newline='') as csvfile:
      ...     reader = csv.DictReader(csvfile)
      ...     for row in reader:
      ...         print(row['first_name'], row['last_name'])
      ...
      Eric Idle
      John Cleese

      >>> print(row)
      {'first_name': 'John', 'last_name': 'Cleese'}

class csv.DictWriter(f, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)

   创建一个对象，该对象在操作上类似常规 writer，但会将字典映射到输出行
   。 *fieldnames* 形参是一个由键组成的 "序列"，它指定字典中要传给
   "writerow()" 方法并写入文件 *f* 的值的顺序。 如果字典没有
   *fieldnames* 中的键，则可选的 *restval* 形参将指明要写入的值。 如果
   传递给 "writerow()" 方法包含的键在 *fieldnames* 中找不到，则可选的
   *extrasaction* 形参将指明要执行的操作。 如果将其设为默认值
   "'raise'"，则会引发 "ValueError"。 如果将其设为 "'ignore'"，则字典
   中额外的值将被忽略。 任何其他可选或关键字参数都将被传递给下层的
   "writer" 实例。

   注意，与 "DictReader" 类不同，"DictWriter" 类的 *fieldnames* 参数不
   是可选参数。

   如果传给 *fieldnames* 的参数是一个迭代器，它将被强制转换为 "list"。

   一个简短的用法示例:

      import csv

      with open('names.csv', 'w', newline='') as csvfile:
          fieldnames = ['first_name', 'last_name']
          writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

          writer.writeheader()
          writer.writerow({'first_name': 'Baked', 'last_name': 'Beans'})
          writer.writerow({'first_name': 'Lovely', 'last_name': 'Spam'})
          writer.writerow({'first_name': 'Wonderful', 'last_name': 'Spam'})

class csv.Dialect

   "Dialect" 类是一个容器类，其属性包含有如何处理双引号、空白符、分隔
   符等的信息。 由于缺少严格的 CSV 规格描述，不同的应用程序会产生略有
   差别的 CSV 数据。  "Dialect" 实例定义了 "reader" 和 "writer" 实例将
   具有怎样的行为。

   所有可用的 "Dialect" 名称会由 "list_dialects()" 返回，并且它们可由
   特定的 "reader" 和 "writer" 类通过它们的初始化函数 ("__init__") 来
   注册，例如:

      import csv

      with open('students.csv', 'w', newline='') as csvfile:
          writer = csv.writer(csvfile, dialect='unix')

class csv.excel

   "excel" 类定义了 Excel 生成的 CSV 文件的常规属性。它在变种注册表中
   的名称是 "'excel'"。

class csv.excel_tab

   "excel_tab" 类定义了 Excel 生成的、制表符分隔的 CSV 文件的常规属性
   。它在变种注册表中的名称是 "'excel-tab'"。

class csv.unix_dialect

   "unix_dialect" 类定义了在 UNIX 系统上生成的 CSV 文件的常规属性，即
   使用 "'\n'" 作为换行符，且所有字段都有引号包围。它在变种注册表中的
   名称是 "'unix'"。

   Added in version 3.2.

class csv.Sniffer

   "Sniffer" 类用于推断 CSV 文件的格式。

   "Sniffer" 类提供了两个方法：

   sniff(sample, delimiters=None)

      分析给定的 *sample* 并返回一个 "Dialect" 子类，该子类中包含了分
      析出的格式参数。如果给出可选的 *delimiters* 参数，则该参数会被解
      释为字符串，该字符串包含了可能的有效定界符。

   has_header(sample)

      分析 sample 文本（假定为 CSV 格式），如果发现其首行为一组列标题
      则返回 "True"。 在检查每一列时，将考虑是否满足两个关键标准之一来
      估计 sample 是否包含标题:

      * 第二至第 n 行包含数字值

      * 第二至第 n 行包含字符串值，其中至少有一个值的长度与该列预期标
        题的长度不同。

      对标题行之后的 21 行进行采样；如果超过半数的列和行满足条件，则返
      回 "True"。

   备注:

     此方法是一个粗略的启发式方式，有可能产生错误的真值和假值。

使用 "Sniffer" 的示例：

   with open('example.csv', newline='') as csvfile:
       dialect = csv.Sniffer().sniff(csvfile.read(1024))
       csvfile.seek(0)
       reader = csv.reader(csvfile, dialect)
       # ... 在此处理 CSV 文件内容 ...

"csv" 模块定义了以下常量：

csv.QUOTE_ALL

   指示 "writer" 对象给所有字段加上引号。

csv.QUOTE_MINIMAL

   指示 "writer" 对象仅对包含特殊字符如 *delimiter*, *quotechar*,
   "'\r'", "'\n'" 或 *lineterminator* 中的任何字符的字段加引号。

csv.QUOTE_NONNUMERIC

   指示 "writer" 对象为所有非数字字段加上引号。

   指示 "reader" 对象将所有未加引号的字段转换为 "float" 类型。

   备注:

     某些数字类型，如 "bool", "Fraction" 或 "IntEnum"，具有不可被转换
     为 "float" 的字符串表示形式。 它们无法在 "QUOTE_NONNUMERIC" 和
     "QUOTE_STRINGS" 模式下被读取。

csv.QUOTE_NONE

   指示 "writer" 对象不对字段加引号。 在现有的 *delimiter*,
   *quotechar*, *escapechar*, "'\r'", "'\n'" 或 *lineterminator* 中的
   任何字符出现在输出数据中的时候它前面会添加当前的 *escapechar*。 如
   果未设置*escapechar*，则在遇到任何需要转义的字符时 writer 都会引发
   "Error"。 将 *quotechar* 设为 "None" 以避免进行转义。

   指示 "reader" 对象不对引号字符执行特殊处理。

csv.QUOTE_NOTNULL

   指示 "writer" 对象为所有不为 "None" 的字段加引号。 这类似于
   "QUOTE_ALL"，区别是如果一个字段值为 "None" 则会写入一个（不带引号的
   ）空字符串。

   指示 "reader" 对象将（不带引号的）空字段解读为 "None" 并在其他情况
   下采取与 "QUOTE_ALL" 相同的行为。

   Added in version 3.12.

csv.QUOTE_STRINGS

   指示 "writer" 对象总是为字符串字段加引号。 这类似于
   "QUOTE_NONNUMERIC"，区别是如果一个字段值为 "None" 则会写入一个（不
   带引号的）空字符串。

   指示 "reader" 对象将（不带引号的）空字符串解读为 "None" 并在其他情
   况下采取与 "QUOTE_NONNUMERIC" 相同的行为。

   Added in version 3.12.

"csv" 模块定义了以下异常：

exception csv.Error

   该异常可能由任何发生错误的函数抛出。


变种与格式参数
==============

为了更容易地指定输入和输出记录的格式，特定的多个格式化形参将组合成为不
同的 dialect。 特定的 dialect 是 "Dialect" 类的一个子类，它包含多个用
于描述 CSV 文件的格式的属性。 当创建 "reader" 或 "writer" 对象时，程序
员可以指定一个字符串或 "Dialect" 类的子类作为 dialect 形参。 作为对
*dialect* 形参的补充或替代，程序员还可以指定单独的格式化形参，它们的名
称与 "Dialect" 类所定义的以下属性相同。

Dialect 类支持以下属性：

Dialect.delimiter

   一个用于分隔字段的单字符，默认为 "','"。

Dialect.doublequote

   控制出现在字段中的 *引号字符* 本身应如何被引出。当该属性为 "True"
   时，双写引号字符。如果该属性为 "False"，则在 *引号字符* 的前面放置
   *转义符*。默认值为 "True"。

   在输出时，如果 *doublequote* 是 "False"，且 *转义符* 未指定，且在字
   段中发现 *引号字符* 时，会抛出 "Error" 异常。

Dialect.escapechar

   被 writer 用来对需要转义的字符进行转义的单字符字符串：

      * 如果 *quoting* 被设为 "QUOTE_NONE" 则 *delimiter*,
        *quotechar*, "'\r'", "'\n'" 以及 *lineterminator* 中的任何字符
        都会被转义；

      * 如果 *doublequote* 为 "False" 则 *quotechar* 会被转义；

      * *escapechar* 本身。

   在读取时，*escapechar* 将从以下字符中去除任何特殊含义。 它默认为
   "None"，表示禁用转义。

   在 3.11 版本发生变更: 不允许空的 *escapechar*。

Dialect.lineterminator

   放在 "writer" 产生的行的结尾，默认为 "'\r\n'"。

   备注:

     "reader" 经过硬编码，会识别 "'\r'" 或 "'\n'" 作为行尾，并忽略
     *lineterminator*。未来可能会更改这一行为。

Dialect.quotechar

   一个单字符形式的用于标示包含特殊字符的字符串，这样的特殊字符有
   *delimiter* 或 *quotechar*，或者包含换行符的字符串 ("'\r'", "'\n'"
   或 *lineterminator* 中的任意字符串)。 默认值为 "'"'"。 当 *quoting*
   设为 "QUOTE_NONE" 时可被设为 "None" 以防止转义 "'"'"。

   在 3.11 版本发生变更: 不允许空的 *quotechar*。

Dialect.quoting

   控制引号何时应由 writer 生成并由 reader 识别。 它可以接受任意
   QUOTE_* 常量 并且如果 *quotechar* 不为 "None" 则默认为
   "QUOTE_MINIMAL"，否则为 "QUOTE_NONE"。

Dialect.skipinitialspace

   当 "True" 时，紧跟在 *delimiter* 后的空格将被忽略。默认值为 "False"
   。当同时使用 "delimiter=' '" 和 "skipinitialspace=True" 时，不允许
   出现未加引号的空字段。

Dialect.strict

   如果为 "True"，则在输入错误的 CSV 时抛出 "Error" 异常。默认值为
   "False"。


Reader 对象
===========

Reader 对象（"DictReader" 实例和 "reader()" 函数返回的对象）具有以下公
开方法：

csvreader.__next__()

   返回 reader 的可迭代对象的下一行，它可以是一个列表（如果对象是由
   "reader()" 返回）或字典（如果是一个 "DictReader" 实例），根据当前
   "Dialect" 来解析。 通常你应当以 "next(reader)" 的形式来调用它。

Reader 对象具有以下公开属性：

csvreader.dialect

   变种描述，只读，供解析器使用。

csvreader.line_num

   源迭代器已经读取了的行数。它与返回的记录数不同，因为记录可能跨越多
   行。

DictReader 对象具有以下公开属性：

DictReader.fieldnames

   字段名称。如果在创建对象时未传入字段名称，则首次访问时或从文件中读
   取第一条记录时会初始化此属性。


Writer 对象
===========

"writer" 对象 ("DictWriter" 实例和 "writer()" 函数所返回的对象 ) 具有
以下公共方法。 对于 "writer" 对象 *row* 必须是输出字符串或数字的可迭代
对象的数字，而对于 "DictWriter" 对象则是一个将文件名映射到字符串或数字
(会先将其传给 "str()") 的字典。 请注意在写入复数时会用圆括号括起来。
这可能会给其他读取 CSV 文件的程序带来一些问题 (假定它们确实支持复数)。

csvwriter.writerow(row, /)

   将 *row* 形参写入到 writer 的文件对象，根据当前 "Dialect" 进行格式
   化。 返回对下层文件对象的 *write* 方法的调用的返回值。

   在 3.5 版本发生变更: 开始支持任意类型的迭代器。

csvwriter.writerows(rows, /)

   将 *rows*（即能迭代出多个上述 *row* 对象的迭代器）中的所有元素写入
   writer 的文件对象，并根据当前设置的变种进行格式化。

Writer 对象具有以下公开属性：

csvwriter.dialect

   变种描述，只读，供 writer 使用。

DictWriter 对象具有以下公开方法：

DictWriter.writeheader()

   在 writer 的文件对象中，写入一行字段名称（字段名称在构造函数中指定
   ），并根据当前设置的变种进行格式化。本方法的返回值就是内部使用的
   "csvwriter.writerow()" 方法的返回值。

   Added in version 3.2.

   在 3.8 版本发生变更: 现在 "writeheader()" 也返回其内部使用的
   "csvwriter.writerow()" 方法的返回值。


例子
====

读取 CSV 文件最简单的一个例子:

   import csv
   with open('some.csv', newline='') as f:
       reader = csv.reader(f)
       for row in reader:
           print(row)

读取其他格式的文件:

   import csv
   with open('passwd', newline='') as f:
       reader = csv.reader(f, delimiter=':', quoting=csv.QUOTE_NONE)
       for row in reader:
           print(row)

相应最简单的写入示例是:

   import csv
   with open('some.csv', 'w', newline='') as f:
       writer = csv.writer(f)
       writer.writerows(someiterable)

由于 "open()" 被用来打开 CSV 文件供读取，因此在默认情况下将使用系统默
认编码格式  (参见 "locale.getencoding()") 把文件解码至 unicode。 要使
用其他编码格式来解码文件，请使用 open 的 "encoding" 参数:

   import csv
   with open('some.csv', newline='', encoding='utf-8') as f:
       reader = csv.reader(f)
       for row in reader:
           print(row)

这同样适用于写入非系统默认编码的内容：打开输出文件时，指定 encoding 参
数。

注册一个新的变种:

   import csv
   csv.register_dialect('unixpwd', delimiter=':', quoting=csv.QUOTE_NONE)
   with open('passwd', newline='') as f:
       reader = csv.reader(f, 'unixpwd')

Reader 的更高级用法——捕获并报告错误:

   import csv, sys
   filename = 'some.csv'
   with open(filename, newline='') as f:
       reader = csv.reader(f)
       try:
           for row in reader:
               print(row)
       except csv.Error as e:
           sys.exit(f'file {filename}, line {reader.line_num}: {e}')

尽管该模块不直接支持解析字符串，但仍可如下轻松完成:

   import csv
   for row in csv.reader(['one,two,three']):
       print(row)

-[ 备注 ]-

[1] 若未指定 "newline=''"，则引号字段内包含的换行符将无法被正确解析，
    且在写入时使用 "\r\n" 换行符的平台上会额外添加一个 "\r"。指定
    "newline=''" 始终是安全的做法，因为 csv 模块会自行进行（*通用换行
    处理*）。
