銀月の符号

Python 使い見習いの日記・雑記

csv モジュールメモ

ひさしぶりに csv モジュールを使ったのだけれど、うろおぼえで十数分無駄にしたので、簡単にメモしてみる。

csv モジュールとは

CSV のようなテキストからデータを読み出したり、書き出したりできる便利モジュール。

CSV って簡単そうに見えるけど、 'a,b,c'.split(',') とかやってしまうのはあまりに無防備。カンマ自身はどのように表現されているのか、など考えるべきことがいくつかある。こういった些細なつまづきをしないために、このモジュールがある。

なお、 csv という名前だけれども、 CSV の「ような」テキストが処理対象なのでタブ区切り、スペース区切りらのテキストにも対応可能。

読むときの小ネタ

CSV の 1 行目がデータでなく各列の説明、見出しになっていることはよくある。

id,name
0,fgshun
1,shun

これを事前に取り出しておくには next を使う。

>>> import csv
>>> f = open(u'spam.csv', 'rb')
>>> reader = csv.reader(f)
>>> fieldnames = next(reader)
>>> for data in reader:
...   print data
...
['0', 'fgshun']
['1', 'shun']
>>> print fieldnames
['id', 'name']

もしくは csv.DictReader を使う。本来、取り出したデータをインデックスアクセスではなく列名アクセスできるようにしてくれる(リストではなく辞書でデータをあつかう)ものだが、 1 行目のデータを列名として解釈してくれるという機能もある(列名を引数で与えなかったときの動作)。

>>> import csv
>>> f = open(u'spam.csv', 'rb')
>>> reader = csv.DictReader(f)
>>> for data in reader:
...   print data
...
{'id': '0', 'name': 'fgshun'}
{'id': '1', 'name': 'shun'}
>>> reader.fieldnames
['id', 'name']

書くときの小ネタ

writer.writerows で複数行を一度に書き込むことができる。

for row in data:
  writer.writerow(row)

writer.writerows(data)

と書くことができるということ。

Windows で用いる際の注意点

ファイルはバイナリモードで開くこと。

細かい書式の差異への対応

csv モジュールはデフォルトでは csv.excel を書式パラメタとして用いる。これは Excel が出力するカンマ区切りファイルに対応するように作られている。

このほか、 csv.excel_tabl という Excel のタブ区切りファイルに対応するものもある。こちらを使うときは、 dialect 引数にこれのインスタンスをわたすようにする。

>>> f = open(u'spam.csv', 'rb')
>>> reader = csv.reader(f, dialect=csv.excel_tab())

書式パラメタを自作するには csv.Dialect クラスを継承したクラスを作成すればよい。作成手順を知るにはこれらのコードを読むのが手っ取り早い。

class Dialect:
    """Describe an Excel dialect.

    This must be subclassed (see csv.excel).  Valid attributes are:
    delimiter, quotechar, escapechar, doublequote, skipinitialspace,
    lineterminator, quoting.

    """
    _name = ""
    _valid = False
    # placeholders
    delimiter = None
    quotechar = None
    escapechar = None
    doublequote = None
    skipinitialspace = None
    lineterminator = None
    quoting = None

    # 省略

class excel(Dialect):
    """Describe the usual properties of Excel-generated CSV files."""
    delimiter = ','
    quotechar = '"'
    doublequote = True
    skipinitialspace = False
    lineterminator = '\r\n'
    quoting = QUOTE_MINIMAL
register_dialect("excel", excel)

class excel_tab(excel):
    """Describe the usual properties of Excel-generated TAB-delimited files."""
    delimiter = '\t'
register_dialect("excel-tab", excel_tab)

各クラス属性の意味はこう。

delimiter
列区切り文字
quotechar
カラム内容をクォートする際に用いられる文字。
escapechar
列区切文字をエスケープする際に用いられる文字(None 可)
doublequote
quotechar 自身をどう処理するか。 True で二重化、 False でエスケープ(escapechar が None の時、実行時エラーとなる可能性あり)
lineterminator
行区切り文字
quoting
クォート条件の指定

quoting に指定できる値は以下。

csv.QUOTE_ALL
無条件ですべてをクォートする
csv.QUOTE_MINIMAL
特殊な文字が含まれているときのみクォートする
csv.QUOTE_NONNUMERIC
非数値のみクォートする(浮動小数点数が多用されたデータ向け)
csv.QUOTE_NONE
特殊な文字をエスケープすることで処理する(escapechar が None の時、実行時エラーとなる可能性あり)

というわけで、たとえば「ExcelCSV っぽいのだけれども各カラムはすべてクォート」という書式はこのような感じで作成できる。

import csv

class ExcelQuoteAll(csv.excel):
    quoting = csv.QUOTE_ALL
>>> from StringIO import StringIO
>>> f = StringIO()
>>> writer = csv.writer(f, ExcelQuoteAll())
>>> writer.writerows((
...   ('spam', 'ham', 'eggs'),
...   ('spam,spam', '"ham"', 'eggs')))
>>> print f.getvalue()
"spam","ham","eggs"
"spam,spam","""ham""","eggs"