csvモジュールがUnicode入力をサポートしていない罠

id:piro_sukeさんがアレコレやってるのを見てちょいと試しているうちに、予想外なところで罠にハマってしまったbonlifeです。同じことで躓く人はあまりいないような気がしますが、備忘録メモです。
csvモジュールで日本語を扱う場合に気をつけないと悲しい気持ちになってしまうかもしれませんよ、というお話。

# -*- coding: cp932 -*-

import sys
import csv
import codecs

print "default encoding : %s" % (sys.getdefaultencoding())

f = codecs.open('out.csv','wb','cp932')
writer = csv.writer(f)

list = [1,2,3,'ダーッ!']
unicode_str_list = map(unicode,map(str,list))
writer.writerow(unicode_str_list)

これはOK。

# -*- coding: cp932 -*-

import sys
import csv
import codecs

print "default encoding : %s" % (sys.getdefaultencoding())

f = codecs.open('out.csv','wb','utf8')
writer = csv.writer(f)

list = [1,2,3,'ダーッ!']
unicode_str_list = map(unicode,map(str,list))
writer.writerow(unicode_str_list)

これもOK。codecs.open() の3つ目の引数に渡す文字コードUTF-8 に変更するだけで、UTF-8 の out.csv が生成されます。
これを踏まえて、次のサンプル。

# -*- coding: cp932 -*-

import sys
import csv
import codecs

print "default encoding : %s" % (sys.getdefaultencoding())

f = codecs.open('out.csv','wb','cp932')
writer = csv.writer(f)

list = [1,2,3,'ダーッ!']
unicode_str_list = [ unicode(str(v),'cp932') for v in list ]
writer.writerow(unicode_str_list)

defaultencoding が ascii の場合、 unicode する際に2つ目の引数で「ほら、僕ってこんな文字コードなんだよ!」って優しく教えてあげないといけないので、リスト内包表記に変更。
で、これをやるとこんなエラーが…。

default encoding : ascii
Traceback (most recent call last):
  File "C:\test\python\csv\sample3.py", line 15, in <module>
    writer.writerow(unicode_str_list)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

処理の流れは

  1. cp932 のデータを準備
  2. Unicode に変換
  3. cp932 として書き出す

となっていて、サンプル1と全く同じなのに、この様ですよ…。で、マニュアルを確認してみる。(って順番がオカシイですが、気にしない方向で。)

注意: このバージョンの csv モジュールは Unicode 入力をサポート していません。また、現在のところ、 ASCII NUL 文字に関連したいくつかの 問題があります。従って、安全を期すには、全ての入力を一般的には印字可能な ASCII にしなければなりません。これらの制限は将来取り去られる ことになっています。

Oh, my gosh...orz defaultencoding が cp932 の時には Unicode の入力を受け付けてくれたのに。あんなに優しかったのに…。急に冷たくされてロンリネス。
ということで、Unicode の便利っぽい雰囲気に惑わされず、男は硬派に書く、という方針に切り替えます。codecs とか使いません。

# -*- coding: cp932 -*-

import csv

f = open('out.csv','wb')
writer = csv.writer(f)

list = [1,2,3,'ダーッ!']
writer.writerow(list)

サンプル1と同じ処理ですが、途中に Unicode を挟んだりしない!これシンプル。動作OK。
じゃあ、サンプル2みたいに文字コードを変えたい場合はどうするのよ!ってことになりますよね。ということで、次。

# -*- coding: cp932 -*-

import csv

f = open('out.csv','wb')
writer = csv.writer(f)

list = [1,2,3,'ダーッ!']
utf8_str_list = [ unicode(str(v),'cp932').encode('utf8') for v in list ]
writer.writerow(utf8_str_list)

なんかあやしい気もするけど、とりあえず動いたっ!一旦、Unicode にした後、忘れないうちに utf8 に encode() してやりましたよ。csvモジュールの writer がどういう動きをするのかは、Pythonのソースまで遡らないと確認できないので、追うのを断念。ここまでやってるうちに、結局何がしたいのか分からなくなってきましたよ。でも、Unicodeまわりの動きについて復習できたのでヨシとします。今後は encode と unicode, decode の変換方向を間違えずに使えそうな予感。