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

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

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

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

The "csv" module implements classes to read and write tabular data in
CSV format.  It allows programmers to say, "write this data in the
format preferred by Excel," or "read data from this file which was
generated by Excel," without knowing the precise details of the CSV
format used by Excel.  Programmers can also describe the CSV formats
understood by other applications or define their own special-purpose
CSV formats.

The "csv" module's "reader" and "writer" objects read and write
sequences.  Programmers can also read and write data in dictionary
form using the "DictReader" and "DictWriter" classes.

参见:

  **PEP 305** - CSV 文件 API
     《Python 增强提议》提出了对 Python 的这一补充。


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

The "csv" module defines the following functions:

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

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

   从 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*，则它将成
   为新的最大字段大小。

The "csv" module defines the following classes:

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 文件内容 ...

The "csv" module defines the following constants:

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.

The "csv" module defines the following exception:

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 模块会自行进行（*通用换行
    处理*）。
