"csv" --- CSV ファイルの読み書き
********************************

**ソースコード:** Lib/csv.py

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

CSV (Comma Separated Values、カンマ区切り値列) と呼ばれる形式は、 スプ
レッドシートやデータベース間でのデータのインポートやエクスポートにおけ
る最も一般的な形式です。 CSVフォーマットは、 **RFC 4180** によって標準
的な方法でフォーマットを記述する試みが行われる以前から長年使用されまし
た。明確に定義された標準がないということは、異なるアプリケーション に
よって生成されたり取り込まれたりするデータ間では、しばしば微妙な違いが
発生するということを意味します。こうした違いのために、複数のデータ源か
ら得られた CSV ファイルを処理する作業が鬱陶しいものになることがありま
す。とはいえ、デリミタ (delimiter) やクオート文字の 相違はあっても、全
体的な形式は十分似通っているため、こうしたデータを効率的に操作し、デー
タの読み書きにおける細々としたことをプログラマ から隠蔽するような単一
のモジュールを書くことは可能です。

"csv" モジュールは、表形式のデータを CSV 形式で読み書きするクラスを実
装しています。 Excel が使う CSV 形式の詳細を知らなくても、「このデータ
を Excel に適した形式で書き込む」ことや「Excel が生成したこのファイル
からデータを読み込む」ことができます。 また、ほかのアプリケーションが
解釈する CSV 形式を記述したり、特定の用途向けに独自の CSV 形式を定義し
たりすることもできます。

"csv" モジュールの "reader" オブジェクトと "writer" オブジェクトは、シ
ーケンスを読み書きします。 "DictReader" クラスと "DictWriter" クラスを
使えば、辞書形式でデータを読み書きすることもできます。

参考:

  **PEP 305** - CSV File API
     Python へのこのモジュールの追加を提案している Python 改良案 (PEP:
     Python Enhancement Proposal)。


モジュールの内容
================

"csv" モジュールは以下の関数を定義しています。

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

   指定された *csvfile* の行を処理する reader オブジェクト を返します
   。 "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

   ここで、"eggs.csv" の内容は次のとおりです:

      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)を指定することができます。 *dialect* パラ
   メータは "Dialect" クラスのサブクラスのインスタンスか、
   "list_dialects()" 関数が返す文字列の1つにすることができます。別のオ
   プション引数である *fmtparams* キーワード引数は、現在の表現形式にお
   ける個々の書式パラメータを上書きするために与えることができます。
   dialect と書式パラメータについての詳細は、 表現形式と書式パラメータ
   節を参照してください。 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'])

   これは、次の内容の "eggs.csv" を書き出します:

      Spam Spam Spam Spam Spam |Baked Beans|
      Spam |Lovely Spam| |Wonderful Spam|

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

   *dialect* を *name* と関連付けます。 *name* は文字列でなければなり
   ません。表現形式(dialect)は "Dialect" のサブクラスを渡すか、または
   キーワード引数 *fmtparams* 、もしくは両方で指定できますが、キーワー
   ド引数の方が優先されます。表現形式と書式化パラメータについての詳細
   は、 表現形式と書式パラメータ 節を参照してください。

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" にマッ
   プするオブジェクトを生成します。マップのキーは省略可能な
   *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'}

   "names.csv" の内容は次のとおりです:

      first_name,last_name
      Eric,Idle
      John,Cleese

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

   通常の writer のように動作しますが、辞書を出力行にマップするオブジ
   ェクトを生成します。 *fieldnames* パラメータは、"writerow()" メソッ
   ドに渡された辞書の値がどのような順番でファイル *f* に書かれるかを指
   定するキーの "sequence" です。 省略可能な *restval* パラメータは、
   辞書に *fieldnames* のキーがない場合に書き込む値を指定します。
   "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'})

   これは、次の内容の "names.csv" を書き出します:

      first_name,last_name
      Baked,Beans
      Lovely,Spam
      Wonderful,Spam

class csv.Dialect

   "Dialect" クラスはコンテナクラスです。その属性に、ダブルクォート、
   空白文字、デリミタなどの扱い方に関する情報を含みます。CSV には厳密
   な規格がないため、アプリケーションによって生成される CSV データはそ
   れぞれ僅かに異なります。 "Dialect" クラスのインスタンスは "reader"
   と "writer" のインスタンスの挙動を定義します。

   利用可能な "Dialect" の名前はすべて "list_dialects()" で取得できま
   す。 次のように初期化関数 ("__init__") を通じて、個々の "reader" ク
   ラスや "writer" クラスに表現形式を指定できます。

      import csv

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

class csv.excel

   "excel" クラスは Excel で生成される CSV ファイルの通常のプロパティ
   を定義します。これは "'excel'" という名前の dialect として登録され
   ています。

class csv.excel_tab

   "excel_tab" クラスは Excel で生成されるタブ分割ファイルの通常のプロ
   パティを定義します。これは "'excel-tab'" という名前の dialect とし
   て登録されています。

class csv.unix_dialect

   "unix_dialect" クラスは UNIX システムで生成される CSV ファイルの通
   常のプロパティ (行終端記号として "'\n'" を用い全てのフィールドをク
   ォートするもの) を定義します。これは "'unix'" という名前の dialect
   として登録されています。

   Added in version 3.2.

class csv.Sniffer

   "Sniffer" クラスは CSV ファイルの書式を推理するために用いられるクラ
   スです。

   "Sniffer" クラスではメソッドを二つ提供しています:

   sniff(sample, delimiters=None)

      与えられた *sample* を解析し、発見されたパラメータを反映した
      "Dialect" サブクラスを返します。オプションの *delimiters* パラメ
      ータを与えた場合、有効なデリミタ文字を含んでいるはずの文字列とし
      て解釈されます。

      複数のデリミタがサンプルに同じ程度に適合する場合 (例えば、"','"
      と "';'" のどちらでもすべての行を一貫して分割できる場合)、各デリ
      ミタの出現回数にかかわらず、"preferred" 属性に並んだ順に優先され
      ます。

   has_header(sample)

      CSV 形式であると想定したサンプルテキストを解析し、最初の行が列見
      出しの並びと思われる場合に "True" を返します。 各列を調べ、次の
      2 つの主要な基準のいずれかに基づいて、サンプルに見出しが含まれる
      かを推定します:

      * 2番目からn番目の行は数値を含みます。

      * the second through n-th rows contain strings where at least
        one value's length differs from that of the putative header of
        that column.

      Twenty-one rows after the header are sampled; if more than half
      of the columns + rows meet the criteria, "True" is returned.

   注釈:

     このメソッドは大雑把なヒューリスティックであり、結果が偽陽性や偽
     陰性である可能性があります。

   "Sniffer" クラスには以下の属性があります:

   preferred

      同程度に適合する候補がある場合に優先するデリミタを、優先順に並べ
      たリストです。 変更できます。 初期値は "[',', '\t', ';', ' ',
      ':']" です。

"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* に含まれる文字などの特殊文字を含むフィールドだけを
   クォートするよう指示します。 *doublequote* が "False" で
   *escapechar* が設定されている場合、*quotechar* はエスケープされ、そ
   の文字を理由としてフィールドがクォートされることはありません。

csv.QUOTE_NONNUMERIC

   "writer" オブジェクトに対し、全ての非数値フィールドをクオートするよ
   うに指示します。

   "reader" オブジェクトに、クォーテーションで囲まれていないすべてのフ
   ィールドを "float" 型に変換するよう指示します。

   注釈:

     Some numeric types, such as "bool", "Fraction", or "IntEnum",
     have a string representation that cannot be converted to "float".
     They cannot be read in the "QUOTE_NONNUMERIC" and "QUOTE_STRINGS"
     modes.

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" クラスのサブクラ
スであり、CSV ファイルの形式を記述するさまざまな属性を持ちます。
"reader" オブジェクトや "writer" オブジェクトを作成する際、"dialect"
引数には文字列か "Dialect" クラスのサブクラスを指定できます。
*dialect* 引数に加えて、またはその代わりに、書式パラメータを個別に指定
することもできます。 各パラメータの名前は、以下で定義する "Dialect" ク
ラスの属性名と同じです。

Dialect は以下の属性をサポートしています:

Dialect.delimiter

   フィールド間を分割するのに用いられる 1 文字からなる文字列です。デフ
   ォルトでは "','" です。

Dialect.doublequote

   フィールド内に現れた *quotechar* のインスタンスで、クオートではない
   その文字自身でなければならない文字をどのようにクオートするかを制御
   します。 "True" の場合、この文字は二重化されます。 "False" の場合、
   *escapechar* は *quotechar* の前に置かれます。デフォルトでは "True"
   です。

   出力においては、 *doublequote* が "False" で *escapechar* がセット
   されていない場合、フィールド内に *quotechar* が現れると "Error" が
   送出されます。

Dialect.escapechar

   writer がエスケープを要する以下の文字をエスケープするために使う、1
   文字の文字列です:

      * *quoting* が "QUOTE_NONE" の場合は、*delimiter*、*quotechar*、
        "'\r'"、"'\n'"、および *lineterminator* に含まれる文字。

      * *doublequote* が "False" の場合は *quotechar*。

      * *escapechar* 自身。

   読み込み時には、*escapechar* は直後の文字の特殊な意味を取り除きます
   。 デフォルトは "None" で、この場合エスケープは無効です。

   バージョン 3.10 で変更: 以前は *escapechar* 自身がエスケープされず
   、読み込み時に失われていました。

   バージョン 3.11 で変更: 空の *escapechar* は許可されていません。

Dialect.lineterminator

   "writer" が作り出す各行を終端する際に用いられる文字列です。デフォル
   トでは "'\r\n'" です。

   注釈:

     "reader" は "'\r'" または "'\n'" のどちらかを行末と認識するように
     ハードコードされており、 *lineterminator* を無視します。この振る
     舞いは将来変更されるかもしれません。

Dialect.quotechar

   *delimiter* や *quotechar* などの特殊文字、または改行文字 ("'\r'"、
   "'\n'"、*lineterminator* に含まれる文字) を含むフィールドをクォート
   するための、1 文字の文字列です。 デフォルトは "'"'" です。
   *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()" 関数
によって返されたオブジェクト) は、以下の public なメソッドを持っていま
す:

csvreader.__next__()

   reader の反復可能なオブジェクトから、現在の表現形式 ("Dialect") に
   基づいて次の行を解析してリスト（オブジェクトが "reader()" から返さ
   れた場合）または辞書 （ "DictReader" のインスタンスの場合）として返
   します。通常は "next(reader)" のようにして呼び出すことになります。

reader オブジェクトには以下の公開属性があります:

csvreader.dialect

   パーサで使われる表現形式の読み出し専用の記述です。

csvreader.line_num

   ソースイテレータから読んだ行数です。この数は返されるレコードの数と
   は、レコードが複数行に亘ることがあるので、一致しません。

DictReader オブジェクトは、以下の public な属性を持っています:

DictReader.fieldnames

   オブジェクトを生成するときに渡されなかった場合、この属性は最初のア
   クセス時か、ファイルから最初のレコードを読み出したときに初期化され
   ます。


writer オブジェクト
===================

"writer" オブジェクト("DictWriter" インスタンス、および  "writer()" 関
数によって返されたオブジェクト) は、以下の public なメソッドを持ってい
ます: *row* には、 "writer" オブジェクトの場合には文字列か数値のイテラ
ブルを指定し、 "DictWriter" オブジェクトの場合はフィールド名をキーとし
て対応する文字列か数値を格納した辞書オブジェクトを指定します(数値は
"str()" で変換されます)。複素数を出力する場合、値をかっこで囲んで出力
します。このため、CSV ファイルを読み込むアプリケーションで（そのアプリ
ケーションが複素数をサポートしていたとしても）問題が発生する場合があり
ます。

csvwriter.writerow(row, /)

   現在の表現形式 ("Dialect".)に沿ってフォーマットされた *row* パラメ
   ータを writer のファイルオブジェクトに書き込みます。ファイルオブジ
   ェクトの *write* メソッドを呼び出した際の戻り値を返します。

   バージョン 3.5 で変更: 任意のイテラブルのサポートの追加。

csvwriter.writerows(rows, /)

   *rows* 引数 (上で解説した *row* オブジェクトのイテラブル) の全ての
   要素を現在の表現形式に基づいて書式化し、writer のファイルオブジェク
   トに書き込みます。

writer オブジェクトには以下の公開属性があります:

csvwriter.dialect

   writer で使われる表現形式の読み出し専用の記述です。

DictWriter のオブジェクトは以下の public メソッドを持っています:

DictWriter.writeheader()

   コンストラクタで指定したフィールド名を、現在の表現形式に従って整形
   し、writer のファイルオブジェクトに 1 行書き込みます。 内部で呼び出
   す "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)

CSV ファイルを読み込み用に開くには "open()" を使うため、デフォルトでは
UTF-8 を使用してファイルを Unicode にデコードします。 別のエンコーディ
ングでデコードするには、"open()" の "encoding" 引数を使ってください。

   import csv
   with open('some.csv', newline='', encoding='latin-1') 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" が追加されます。 csv モジュール自身が
    (*ユニバーサル*) 改行処理を行うので、"newline=''" は常に指定して差
    し支えありません。
