Python on win32でバイナリデータを出力する際の注意点
Python on win32のurllibでダウンロードしたZIPファイルが壊れていて、調べてみたら改行コードが変わってしまっていた件の原因がわかりました。問題点が想像していた場所と異なったことと、Pythonのreprが改行コードを含むファイルを表示する際の特徴とがかぶって問題をややこしくしてしまっていたようです。
問題点および解決策を一言で説明するとPythonでバイナリデータをファイル出力する際には"w"ではなく"wb"(バイナリ書き込みモード)でファイルを開かなければならないと言うことです。こんなことに今頃つまずいて自分でもびっくり。今まで一度もバイナリファイルをあつかったことがなかったんですね。
以下のソースは、試しにいろいろな改行コードをファイルに書きだし、それを読み込んで表示するものです。
fo = file("c:\\test.zip", "w")
fo.write("a(\x0A)")
fo.write("d(\x0D)")
fo.write("da(\x0D\x0A)")
fo.write("ad(\x0A\x0D)")
fo.close()
fi = file("c:\\test.zip")
data = fi.read()
fi.close()
print repr(data)
出力結果は'a(\n)d(\r)da(\n)ad(\n\r)'。これを見て「\nが\x0Dで\rが\x0Aだな」と思ったのが勘違いの始まり。これをウェブサーバにアップロードしてからurllibでダウンロードすると以下のようになります。
>>> import urllib
>>> urllib.urlopen("http://www.nishiohirokazu.org/files/test.zip").read()
'a(\r\n)d(\r)da(\r\r\n)ad(\r\n\r)'
これを見てurllibの使い方が間違っているものと思って悩んでいたのですよ。実際はファイルの出力が問題だったわけです。しかしバイナリモード("wb")で出力してもテキストモード("w")で出力しても、それを読み込んでreprすると「'a(\n)d(\r)da(\n)ad(\n\r)'」と表示されてしまいます。ファイルサイズはバイナリモードで書き込んだものが20バイトで、アスキーモードで書き込んだものは23バイトなので、この時点で改行コードがおかしくなっているのは間違いありません。しかし、Windows用Pythonが"\x0A\x0D"のことを「これはWindowsの改行だから」ということでか「\n」と表示し、しかもただの"\x0D"もUNIX本来の改行なので「\n」と表示してしまうので気づけませんでした。
難しいですね。「Windowsの改行コードは"\r\n"」で統一してしまえば誤解の余地はないのですが、波及範囲が広いので今更変更はできないのでしょう。