13.1. csv --- CSV ファイルの読み書き

バージョン 2.3 で追加.

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

csv モジュールでは、CSV 形式で書かれたテーブル状のデータを読み書きするためのクラスを実装しています。このモジュールを使うことで、プログラマは Excel で使われている CSV 形式に関して詳しい知識をもっていなくても、 "このデータを Excel で推奨されている形式で書いてください" とか、 "データを Excel で作成されたこのファイルから読み出してください" と言うことができます。プログラマはまた、他のアプリケーションが解釈できる CSV 形式を記述したり、独自の特殊な目的をもった CSV 形式を定義することができます。

csv モジュールの reader および writer オブジェクトはシーケンス型を読み書きします。プログラマは DictReaderDictWriter クラスを使うことで、データを辞書形式で読み書きすることもできます。

注釈

このバージョンの csv モジュールは Unicode 入力をサポートしていません。また、現在のところ、 ASCII NUL 文字に関連したいくつかの問題があります。従って、安全を期すには、全ての入力を UTF-8 または印字可能な ASCII にしなければなりません。これについては 使用例 節の例を参照してください。

参考

PEP 305 - CSV File API

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

13.1.1. モジュールコンテンツ

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

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

与えられた csvfile 内の行を反復処理するような reader オブジェクトを返します。 csvfile はイテレータ(iterator)プロトコル をサポートし、 next() メソッドが呼ばれた際に常に文字列を返すような任意のオブジェクトにすることができます --- ファイルオブジェクトでも リストでも構いません。 csvfile がファイルオブジェクトの場合、ファイルオブジェクトの形式に違いがあるようなプラットフォームでは 'b' フラグを付けて開かなければなりません。オプションとして dialect パラメタを与えることができ、特定の CSV 表現形式 (dialect) 特有のパラメタの集合を定義するために使われます。 dialect パラメタは Dialect クラスのサブクラス のインスタンスか、 list_dialects() 関数が返す文字列の一つにすることができます。別のオプションである fmtparams キーワード引数は、現在の表現形式における個々の書式パラメタを上書きするために与えることができます。表現形式および書式化パラメタの詳細については、 Dialect クラスと書式化パラメタ 節を参照してください。

csv ファイルから読み込まれた各行は、文字列のリストとして返されます。 データ型の変換が自動的に行われることはありません。

短い利用例:

>>> import csv
>>> with open('eggs.csv', 'rb') 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

バージョン 2.5 で変更: パーサが複数行に亘るクオートされたフィールドに関して厳格になりました。以前は、クオートされたフィールドの中で終端の改行文字無しに行が終わった場合、 返されるフィールドには改行が挿入されていましたが、この振る舞いはフィールドの中に復帰文字を含むようなファイルを読むときに問題を起こしていました。そこでフィールドに改行文字を挿入せずに返すように改められました。この結果、フィールドに埋め込まれた改行文字が重要ならば、入力は改行文字を保存するような仕方で複数行に分割されるはずです。

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

ユーザが与えたデータをデリミタで区切られた文字列に変換し、与えられたファイルオブジェクトに書き込むための writer オブジェクトを返します。 csvfilewrite() メソッドを持つ任意のオブジェクトです。 csvfile がファイルオブジェクトの場合、 'b' フラグが意味を持つプラットフォームでは 'b' フラグを付けて開かなければなりません。 オプションとして dialect 引数を与えることができ、利用する CSV 表現形式 (dialect) を指定することができます。 dialect パラメタは Dialect クラスのサブクラスのインスタンスか、 list_dialects() 関数が返す文字列の 1 つにすることができます。 別のオプション引数である fmtparams キーワード引数は、現在の表現形式における個々の書式パラメタを上書きするために与えることができます。 dialect と書式パラメタについての詳細は、 Dialect クラスと書式化パラメタ 節を参照してください。 DB API を実装するモジュールとのインタフェースを可能な限り容易にするために、 None は空文字列として書き込まれます。 この処理は可逆な変換ではありませんが、SQL で NULL データ値を CSV にダンプする処理を、 cursor.fetch* 呼び出しによって 返されたデータを前処理することなく簡単に行うことができます。 浮動小数点数は、書き出される前に repr() を使って文字列に変換されます。 他の非文字列データは、書き出される前に str() を使って文字列に変換されます。

短い利用例:

import csv
with open('eggs.csv', 'wb') 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, ]**fmtparams)

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

csv.unregister_dialect(name)

name に関連づけられた表現形式を表現形式レジストリから削除します。 name が表現形式名でない場合には Error を送出します。

csv.get_dialect(name)

name に関連づけられた表現形式を返します。 name が表現形式名でない場合には Error を送出します。

バージョン 2.5 で変更: この関数は immutableDialect クラスを返します。 以前は、要求された dialect のインスタンスが返されていました。 ユーザーはクラスを操作すうることで、アクティブな reader や writer の動作を変更することができます。

csv.list_dialects()

登録されている全ての表現形式を返します。

csv.field_size_limit([new_limit])

パーサが許容する現在の最大フィールドサイズを返します。 new_limit が渡されたときは、その値が新しい上限になります。

バージョン 2.5 で追加.

csv モジュールでは以下のクラスを定義しています:

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

Create an object which operates like a regular reader but maps the information read into a dict whose keys are given by the optional fieldnames parameter. The fieldnames parameter is a sequence whose elements are associated with the fields of the input data in order. These elements become the keys of the resulting dictionary. If the fieldnames parameter is omitted, the values in the first row of the file f will be used as the fieldnames. If the row read has more fields than the fieldnames sequence, the remaining data is added as a sequence keyed by the value of restkey. If the row read has fewer fields than the fieldnames sequence, the remaining keys take the value of the optional restval parameter. Any other optional or keyword arguments are passed to the underlying reader instance.

短い利用例:

>>> import csv
>>> with open('names.csv') as csvfile:
...     reader = csv.DictReader(csvfile)
...     for row in reader:
...         print(row['first_name'], row['last_name'])
...
Baked Beans
Lovely Spam
Wonderful Spam
class csv.DictWriter(f, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)

Create an object which operates like a regular writer but maps dictionaries onto output rows. The fieldnames parameter is a sequence of keys that identify the order in which values in the dictionary passed to the writerow() method are written to the file f. The optional restval parameter specifies the value to be written if the dictionary is missing a key in fieldnames. If the dictionary passed to the writerow() method contains a key not found in fieldnames, the optional extrasaction parameter indicates what action to take. If it is set to 'raise' a ValueError is raised. If it is set to 'ignore', extra values in the dictionary are ignored. Any other optional or keyword arguments are passed to the underlying writer instance.

Note that unlike the DictReader class, the fieldnames parameter of the DictWriter is not optional. Since Python's dict objects are not ordered, there is not enough information available to deduce the order in which the row should be written to the file f.

短い利用例:

import csv

with open('names.csv', 'w') 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 クラスはコンテナクラスで、基本的な用途としては、その属性を特定の readerwriter インスタンスのパラメタを定義するために用います。

class csv.excel

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

class csv.excel_tab

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

class csv.Sniffer

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

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

sniff(sample, delimiters=None)

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

has_header(sample)

(CSV 形式と仮定される) サンプルテキストを解析して、最初の行がカラムヘッダの羅列のように推察される場合 True を返します。

Sniffer の利用例:

with open('example.csv', 'rb') as csvfile:
    dialect = csv.Sniffer().sniff(csvfile.read(1024))
    csvfile.seek(0)
    reader = csv.reader(csvfile, dialect)
    # ... process CSV file contents here ...

csv モジュールでは以下の定数を定義しています:

csv.QUOTE_ALL

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

csv.QUOTE_MINIMAL

writer オブジェクトに対し、 delimiterquotechar または lineterminator に含まれる任意の文字のような特別な文字を含むフィールドだけをクオートするように指示します。

csv.QUOTE_NONNUMERIC

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

reader に対しては、クオートされていない全てのフィールドを float 型に変換するよう指示します。

csv.QUOTE_NONE

writer オブジェクトに対し、フィールドを決してクオートしないように指示します。現在の delimiter が出力データ中に現れた場合、現在設定されている escapechar 文字が前に付けられます。 escapechar がセットされていない場合、エスケープが必要な文字に遭遇した writer は Error を送出します。

reader に対しては、クオート文字の特別扱いをしないように指示します。

csv モジュールでは以下の例外を定義しています:

exception csv.Error

全ての関数において、エラーが検出された際に送出される例外です。

13.1.2. Dialect クラスと書式化パラメタ

レコードに対する入出力形式の指定をより簡単にするために、特定の書式化パラメタは表現形式 (dialect) にまとめてグループ化されます。表現形式は Dialect クラスのサブクラスで、様々なクラス特有のメソッドと、 validate() メソッドを一つ持っています。 reader または writer オブジェクトを生成するとき、プログラマは文字列または Dialect クラスのサブクラスを表現形式パラメタとして渡さなければなりません。さらに、 dialect パラメタの代りに、プログラマは上で定義されている属性と同じ名前を持つ個々の書式化パラメタを Dialect クラスに指定することができます。

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

Dialect.delimiter

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

Dialect.doublequote

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

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

Dialect.escapechar

writer が、 quotingQUOTE_NONE に設定されている場合に delimiter をエスケープするため、および、 doublequoteFalse の場合に quotechar をエスケープするために用いられる、 1 文字からなる文字列です。読み込み時には escapechar はそれに引き続く文字の特別な意味を取り除きます。デフォルトでは None で、エスケープを行ないません。

Dialect.lineterminator

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

注釈

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

Dialect.quotechar

delimiterquotechar といった特殊文字を含むか、改行文字を含むフィールドをクオートする際に用いられる 1 文字からなる文字列です。デフォルトでは '"' です。

Dialect.quoting

クオートがいつ writer によって生成されるか、また reader によって認識されるかを制御します。 QUOTE_* 定数のいずれか (モジュールコンテンツ 節参照) をとることができ、デフォルトでは QUOTE_MINIMAL です。

Dialect.skipinitialspace

True の場合、 delimiter の直後に続く空白は無視されます。デフォルトでは False です。

Dialect.strict

True の場合、 不正な CSV 入力に対して Error を送出します。デフォルトでは False です。

13.1.3. reader オブジェクト

reader オブジェクト(DictReader インスタンス、および reader() 関数によって返されたオブジェクト) は、以下の public なメソッドを持っています:

csvreader.next()

reader の反復可能なオブジェクトから、現在の表現形式に基づいて次の行を解析して返します。

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

csvreader.dialect

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

csvreader.line_num

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

バージョン 2.5 で追加.

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

csvreader.fieldnames

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

バージョン 2.6 で変更.

13.1.4. writer オブジェクト

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

csvwriter.writerow(row)

row パラメタを現在の表現形式に基づいて書式化し、 writer のファイルオブジェクトに書き込みます。

csvwriter.writerows(rows)

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

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

csvwriter.dialect

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

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

DictWriter.writeheader()

(コンストラクタで指定された)フィールド名の行を出力します。

バージョン 2.7 で追加.

13.1.5. 使用例

最も簡単な CSV ファイル読み込みの例です:

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

別の書式での読み込み:

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

上に対して、単純な書き込みのプログラム例は以下のようになります。

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

新しい表現形式の登録:

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

もう少し手の込んだ reader の使い方 --- エラーを捉えてレポートします。

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

このモジュールは文字列の解析は直接サポートしませんが、簡単にできます。

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

csv モジュールは直接は Unicode の読み書きをサポートしませんが、 ASCII NUL 文字に関わる問題のために8ビットクリーンに書き込みます。ですから、NUL を使う UTF-16 のようなエンコーディングを避ける限り エンコード・デコードを行なう関数やクラスを書くことができます。 UTF-8 がお勧めです。

以下の unicode_csv_reader() は Unicode の CSV データ (Unicode 文字列のリスト)を扱うための csv.reader をラップするジェネレータ(generator)です。 utf_8_encoder() は一度に 1 文字列(または行) ずつ Unicode 文字列を UTF-8 としてエンコードするジェネレータです。エンコードされた文字列は CSV reader により分解され、 unicode_csv_reader() が UTF-8 エンコードの分解された文字列をデコードして Unicode に戻します。

import csv

def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs):
    # csv.py doesn't do Unicode; encode temporarily as UTF-8:
    csv_reader = csv.reader(utf_8_encoder(unicode_csv_data),
                            dialect=dialect, **kwargs)
    for row in csv_reader:
        # decode UTF-8 back to Unicode, cell by cell:
        yield [unicode(cell, 'utf-8') for cell in row]

def utf_8_encoder(unicode_csv_data):
    for line in unicode_csv_data:
        yield line.encode('utf-8')

その他のエンコーディングには以下の UnicodeReader クラスと UnicodeWriter クラスが使えます。二つのクラスは encoding パラメータをコンストラクタで取り、本物の reader や writer に渡されるデータが UTF-8 でエンコードされていることを保証します。

import csv, codecs, cStringIO

class UTF8Recoder:
    """
    Iterator that reads an encoded stream and reencodes the input to UTF-8
    """
    def __init__(self, f, encoding):
        self.reader = codecs.getreader(encoding)(f)

    def __iter__(self):
        return self

    def next(self):
        return self.reader.next().encode("utf-8")

class UnicodeReader:
    """
    A CSV reader which will iterate over lines in the CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
        f = UTF8Recoder(f, encoding)
        self.reader = csv.reader(f, dialect=dialect, **kwds)

    def next(self):
        row = self.reader.next()
        return [unicode(s, "utf-8") for s in row]

    def __iter__(self):
        return self

class UnicodeWriter:
    """
    A CSV writer which will write rows to CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
        # Redirect output to a queue
        self.queue = cStringIO.StringIO()
        self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()

    def writerow(self, row):
        self.writer.writerow([s.encode("utf-8") for s in row])
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode("utf-8")
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)