csv – カンマ区切りのファイル¶
目的: | カンマ区切りのファイルを読み書きする |
---|---|
利用できるバージョン: | 2.3 以上 |
csv モジュールは、スプレッドシートやデータベースからデータをエクスポートして、一般的に カンマ区切り (CSV) フォーマットと言う、フィールド(列)とレコード(行)のテキストファイルを処理するのに便利です。カンマは1つのレコードを複数のフィールドに分割するために使用されます。
Note
Python 2.5 の csv モジュールでは Unicode データをサポートしません。”ASCII NUL 文字の問題” もあります。UTF-8 か表示可能な ASCII 文字のみ使用することをお奨めします。
読み込み処理¶
CSV ファイルからデータを読み込んでオブジェクトを作成するには reader() を使用してください。reader は、ファイルを1行ずつ読み込んで処理するイテレータとして使用されます。例えば、次のようになります。
import csv
import sys
f = open(sys.argv[1], 'rt')
try:
reader = csv.reader(f)
for row in reader:
print row
finally:
f.close()
reader() の1番目の引数は、テキスト行のソースを渡します。このサンプルはファイルを渡していますが、繰り返し処理可能なオブジェクト(StringIO インスタンス、リスト等)であれば何でも構いません。その他のオプション引数は、入力データの解析方法を管理するために渡されます。
これは NeoOffice からエクスポートされたサンプルファイルです。
"Title 1","Title 2","Title 3"
1,"a",08/18/07
2,"b",08/19/07
3,"c",08/20/07
4,"d",08/21/07
5,"e",08/22/07
6,"f",08/23/07
7,"g",08/24/07
8,"h",08/25/07
9,"i",08/26/07
1行ずつ読み込みながら、入力データのそれぞれの行は解析されて文字列のリストへ変換されます。
$ python csv_reader.py testdata.csv
['Title 1', 'Title 2', 'Title 3']
['1', 'a', '08/18/07']
['2', 'b', '08/19/07']
['3', 'c', '08/20/07']
['4', 'd', '08/21/07']
['5', 'e', '08/22/07']
['6', 'f', '08/23/07']
['7', 'g', '08/24/07']
['8', 'h', '08/25/07']
['9', 'i', '08/26/07']
パーサは1行の文字列内に組み込まれた改行を扱います。”レコードの行” は、ファイルから入力された “ファイルの行” と必ずしも同じではありません。
"Title 1","Title 2","Title 3"
1,"first line
second line",08/18/07
改行を含む値の入力は、パーサが返すときに内部に改行を含むように保持します。
$ python csv_reader.py testlinebreak.csv
['Title 1', 'Title 2', 'Title 3']
['1', 'first line\nsecond line', '08/18/07']
書き込み処理¶
CSV ファイルの書き込みは、その読み込みと同じぐらい簡単です。 writer() で書き込むためにオブジェクトを作成してから、 writerow() を使用してその行を繰り返し処理します。
import csv
import sys
f = open(sys.argv[1], 'wt')
try:
writer = csv.writer(f)
writer.writerow( ('Title 1', 'Title 2', 'Title 3') )
for i in range(10):
writer.writerow( (i+1, chr(ord('a') + i), '08/%02d/07' % (i+1)) )
finally:
f.close()
print open(sys.argv[1], 'rt').read()
その結果出力は、reader サンプルのエクスポートされたデータと厳密には同じではありません。
$ python csv_writer.py testout.csv
Title 1,Title 2,Title 3
1,a,08/01/07
2,b,08/02/07
3,c,08/03/07
4,d,08/04/07
5,e,08/05/07
6,f,08/06/07
7,g,08/07/07
8,h,08/08/07
9,i,08/09/07
10,j,08/10/07
デフォルトのクォート処理は writer によって違うので、文字列カラムはクォートされません。数値以外の値をクォートするには quoting 引数を追加することで簡単に変更できます。
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
これで文字列はクォートされます。
$ python csv_writer_quoted.py testout_quoted.csv
"Title 1","Title 2","Title 3"
1,"a","08/01/07"
2,"b","08/02/07"
3,"c","08/03/07"
4,"d","08/04/07"
5,"e","08/05/07"
6,"f","08/06/07"
7,"g","08/07/07"
8,"h","08/08/07"
9,"i","08/09/07"
10,"j","08/10/07"
クォート処理¶
csv モジュールで定数として定義された4種類の quoting オプションがあります。
- QUOTE_ALL
- 型に関係なく全てをクォートする
- QUOTE_MINIMAL
- (同じ dialect やオプションで設定されたパーサを混乱させる任意の)特別文字をもつフィールドをクォートする、デフォルトのオプション
- QUOTE_NONNUMERIC
- 整数や浮動小数ではない全てのフィールドをクォートする、reader で使用するとクォートされない入力フィールドは浮動小数に変換される
- QUOTE_NONE
- 何もクォートしない、reader で使用すると引用符はフィールドの値に含まれる(通常は、デリミタのように扱われて取り除かれる)
Dialect 処理¶
カンマ区切りファイルには広く使用されている標準規格がないので、パーサは柔軟に処理する必要があります。この柔軟性のために csv モジュールがデータを解析する、もしくは書き込む方法を管理するパラメータがたくさんあります。しかし、reader や writer へこういったパラメータの1つずつ指定するというよりも、便利な dialect オブジェクトに複数のパラメータがグループ化されています。
dialect クラスは名前で登録されるので、 csv モジュールの呼び出し側は事前にパラメータ設定を知っている必要はありません。登録済みの dialect クラスの全リストは list_dialects() 関数で取り出せます。
import csv
print csv.list_dialects()
標準ライブラリは excel と excel-tabs の2つの dialect を提供します。 excel dialect は、Microsoft Excel のデフォルトエクスポートフォーマットであり、OpenOffice や NeoOffice でも使用します。
$ python csv_list_dialects.py
['excel-tab', 'excel']
Dialect を作成する¶
次のようにフィールドの区切り文字をカンマではなく | を使用します。
"Title 1"|"Title 2"|"Title 3"
1|"first line
second line"|08/18/07
新しい dialect に delimiter を指定して登録できます。
import csv
csv.register_dialect('pipes', delimiter='|')
with open('testdata.pipes', 'r') as f:
reader = csv.reader(f, dialect='pipes')
for row in reader:
print row
このファイルは、カンマ区切りのファイルのように読み込まれます。
$ python csv_dialect.py
['Title 1', 'Title 2', 'Title 3']
['1', 'first line\nsecond line', '08/18/07']
Dialect パラメータ¶
dialect 特性は、データファイルを解析したり、書き込むときに全てのトークンで使用されます。カラムがトークンのエスケープに使用される文字を区切る方法から、全てのファイルフォーマットの特徴が指定できます。
属性 | デフォルト | 内容 |
---|---|---|
delimiter | , | フィールドセパレータ (一文字) |
doublequote | True | 引用符インスタンスを2重にするかどうかの制御フラグ |
escapechar | None | エスケープシーケンスを表す文字 |
lineterminator | \r\n | writer が使用する一行の終わりを表す文字列 |
quotechar | " | 特別な値を含むフィールドを囲むための文字列(一文字) |
quoting | QUOTE_MINIMAL | 前説で説明したクォート処理を制御する |
skipinitialspace | False | フィールド区切り文字の後のスペースを無視する |
import csv
import sys
csv.register_dialect('escaped', escapechar='\\', doublequote=False, quoting=csv.QUOTE_NONE)
csv.register_dialect('singlequote', quotechar="'", quoting=csv.QUOTE_ALL)
quoting_modes = dict( (getattr(csv,n), n) for n in dir(csv) if n.startswith('QUOTE_') )
for name in sorted(csv.list_dialects()):
print '\nDialect: "%s"\n' % name
dialect = csv.get_dialect(name)
print ' delimiter = %-6r skipinitialspace = %r' % (dialect.delimiter,
dialect.skipinitialspace)
print ' doublequote = %-6r quoting = %s' % (dialect.doublequote,
quoting_modes[dialect.quoting])
print ' quotechar = %-6r lineterminator = %r' % (dialect.quotechar,
dialect.lineterminator)
print ' escapechar = %-6r' % dialect.escapechar
print
writer = csv.writer(sys.stdout, dialect=dialect)
for i in xrange(3):
writer.writerow(
('col1', i, '10/%02d/2010' % i,
'Contains special chars: " \' %s to be parsed' % dialect.delimiter)
)
print
このプログラムは、複数の dialect で同じデータがどう処理されるかを表示します。
$ python csv_dialect_variations.py
Dialect: "escaped"
delimiter = ',' skipinitialspace = 0
doublequote = 0 quoting = QUOTE_NONE
quotechar = '"' lineterminator = '\r\n'
escapechar = '\\'
col1,0,10/00/2010,Contains special chars: \" ' \, to be parsed
col1,1,10/01/2010,Contains special chars: \" ' \, to be parsed
col1,2,10/02/2010,Contains special chars: \" ' \, to be parsed
Dialect: "excel"
delimiter = ',' skipinitialspace = 0
doublequote = 1 quoting = QUOTE_MINIMAL
quotechar = '"' lineterminator = '\r\n'
escapechar = None
col1,0,10/00/2010,"Contains special chars: "" ' , to be parsed"
col1,1,10/01/2010,"Contains special chars: "" ' , to be parsed"
col1,2,10/02/2010,"Contains special chars: "" ' , to be parsed"
Dialect: "excel-tab"
delimiter = '\t' skipinitialspace = 0
doublequote = 1 quoting = QUOTE_MINIMAL
quotechar = '"' lineterminator = '\r\n'
escapechar = None
col1 0 10/00/2010 "Contains special chars: "" ' to be parsed"
col1 1 10/01/2010 "Contains special chars: "" ' to be parsed"
col1 2 10/02/2010 "Contains special chars: "" ' to be parsed"
Dialect: "singlequote"
delimiter = ',' skipinitialspace = 0
doublequote = 1 quoting = QUOTE_ALL
quotechar = "'" lineterminator = '\r\n'
escapechar = None
'col1','0','10/00/2010','Contains special chars: " '' , to be parsed'
'col1','1','10/01/2010','Contains special chars: " '' , to be parsed'
'col1','2','10/02/2010','Contains special chars: " '' , to be parsed'
自動的に Dialect を検出する¶
入力ファイルを解析する dialect を設定する最も良い方法は、事前に適切な設定を知っておくことです。dialect パラメータが不明なデータのために、 Sniffer クラスが目星を付けるために使用されます。 sniff() メソッドは入力データのサンプルと、可能性のあるデリミタ文字をオプション引数で受け取ります。
import csv
from StringIO import StringIO
import textwrap
csv.register_dialect('escaped', escapechar='\\', doublequote=False, quoting=csv.QUOTE_NONE)
csv.register_dialect('singlequote', quotechar="'", quoting=csv.QUOTE_ALL)
# 全ての dialect が分かっているサンプルデータを生成する
samples = []
for name in sorted(csv.list_dialects()):
buffer = StringIO()
dialect = csv.get_dialect(name)
writer = csv.writer(buffer, dialect=dialect)
for i in xrange(3):
writer.writerow(
('col1', i, '10/%02d/2010' % i,
'Contains special chars: " \' %s to be parsed' % dialect.delimiter)
)
samples.append( (name, dialect, buffer.getvalue()) )
# サンプルの dialect を推測して、その結果をデータ解析に使用する
sniffer = csv.Sniffer()
for name, expected, sample in samples:
print '\nDialect: "%s"\n' % name
dialect = sniffer.sniff(sample, delimiters=',\t')
reader = csv.reader(StringIO(sample), dialect=dialect)
for row in reader:
print row
sniff() は、データの解析に使用される設定をもつ Dialect インスタンスを返します。その実行結果は、このサンプルの “escaped” dialect で紹介するように、必ずしも完全ではないかもしれません。
$ python csv_dialect_sniffer.py
Dialect: "escaped"
['col1', '0', '10/00/2010', 'Contains special chars: \\" \' \\', ' to be parsed']
['col1', '1', '10/01/2010', 'Contains special chars: \\" \' \\', ' to be parsed']
['col1', '2', '10/02/2010', 'Contains special chars: \\" \' \\', ' to be parsed']
Dialect: "excel"
['col1', '0', '10/00/2010', 'Contains special chars: " \' , to be parsed']
['col1', '1', '10/01/2010', 'Contains special chars: " \' , to be parsed']
['col1', '2', '10/02/2010', 'Contains special chars: " \' , to be parsed']
Dialect: "excel-tab"
['col1', '0', '10/00/2010', 'Contains special chars: " \' \t to be parsed']
['col1', '1', '10/01/2010', 'Contains special chars: " \' \t to be parsed']
['col1', '2', '10/02/2010', 'Contains special chars: " \' \t to be parsed']
Dialect: "singlequote"
['col1', '0', '10/00/2010', 'Contains special chars: " \' , to be parsed']
['col1', '1', '10/01/2010', 'Contains special chars: " \' , to be parsed']
['col1', '2', '10/02/2010', 'Contains special chars: " \' , to be parsed']
フィールド名を使用する¶
シーケンスデータの処理に加えて、 csv モジュールは、フィールドに名前を付けてディクショナリとして行を処理するクラスを提供します。 DictReader と DictWriter クラスは、行をリストではなくディクショナリとして変換します。ディクショナリのキーは引数で渡されるか、(ヘッダを含むときに)入力データの先頭行から推定されます。
import csv
import sys
f = open(sys.argv[1], 'rt')
try:
reader = csv.DictReader(f)
for row in reader:
print row
finally:
f.close()
ディクショナリベースの reader や writer は、同じメソッドや引数を使用して、シーケンスベースのクラスのラッパーとして実装されます。reader API の唯一の違いは、その行がリストやタプルではなくディクショナリとして返されることです。
$ python csv_dictreader.py testdata.csv
{'Title 1': '1', 'Title 3': '08/18/07', 'Title 2': 'a'}
{'Title 1': '2', 'Title 3': '08/19/07', 'Title 2': 'b'}
{'Title 1': '3', 'Title 3': '08/20/07', 'Title 2': 'c'}
{'Title 1': '4', 'Title 3': '08/21/07', 'Title 2': 'd'}
{'Title 1': '5', 'Title 3': '08/22/07', 'Title 2': 'e'}
{'Title 1': '6', 'Title 3': '08/23/07', 'Title 2': 'f'}
{'Title 1': '7', 'Title 3': '08/24/07', 'Title 2': 'g'}
{'Title 1': '8', 'Title 3': '08/25/07', 'Title 2': 'h'}
{'Title 1': '9', 'Title 3': '08/26/07', 'Title 2': 'i'}
DictWriter は、出力のカラムの順番通りにフィールド名のリストを渡さなければなりません。
import csv
import sys
f = open(sys.argv[1], 'wt')
try:
fieldnames = ('Title 1', 'Title 2', 'Title 3')
writer = csv.DictWriter(f, fieldnames=fieldnames)
headers = dict( (n,n) for n in fieldnames )
writer.writerow(headers)
for i in range(10):
writer.writerow({ 'Title 1':i+1,
'Title 2':chr(ord('a') + i),
'Title 3':'08/%02d/07' % (i+1),
})
finally:
f.close()
print open(sys.argv[1], 'rt').read()
$ python csv_dictwriter.py testout.csv
Title 1,Title 2,Title 3
1,a,08/01/07
2,b,08/02/07
3,c,08/03/07
4,d,08/04/07
5,e,08/05/07
6,f,08/06/07
7,g,08/07/07
8,h,08/08/07
9,i,08/09/07
10,j,08/10/07